import { valueFormatter as vf , textMeasurementService as tms } from "powerbi-visuals-utils-formattingutils" ; import TextProperties = tms . TextProperties ; import textMeasurementService = tms . textMeasurementService ; import valueFormatter = vf . valueFormatter ; import * as Utils from "./jsUtils" ; import * as Calculator from "./calculator" ; import { Visual } from './visual' ; export class Renderer { private visual : Visual ; private tableDefinition : any ; constructor ( visual ) { this . visual = visual ; } private RenderNoContentText ( target ) { var t = document . createTextNode ( "No table definition defined. Edit the table definition by pressing the edit link in the upper right menu." ) ; target . appendChild ( t ) ; } private RenderNonNumericColumns ( target ) { var t = document . createTextNode ( "Columns other than the first one contains non-numeric fields. This is not allowed." ) ; target . appendChild ( t ) ; } private GetValueForColumnRowCalculationByIndex ( row , colIndex , colDef , modelRow ) { var model = this . visual . getModel ( ) ; var fExpression = row . formula ; for ( let m of model ) { if ( row . formula !== null && m . name !== null ) { var iPos = row . formula . toLowerCase ( ) . indexOf ( m . name . toLowerCase ( ) ) ; if ( iPos !== - 1 ) { var modelRawValue = m . values [ colIndex ] . rawValue ; fExpression = Utils . replace2 ( fExpression , m . name , modelRawValue ) ; } } } var rawValue = Calculator . EvalFormula ( fExpression ) ; var format = modelRow . values [ colIndex ] . formatString ; if ( Utils . containsValue ( colDef . format ) ) { format = colDef . format ; } if ( Utils . containsValue ( row . format ) ) { format = row . format ; } var formattedValue = this . FormatValue ( rawValue , format ) ; return { formattedValue , rawValue , modelRawValue , format } ; } private FormatValue ( rawValue , format ) { var customFormatter = null ; if ( Utils . containsValue ( this . tableDefinition . culture ) ) { customFormatter = valueFormatter . create ( { cultureSelector : "zh-TW" , format } ) ; } else { customFormatter = valueFormatter . create ( { cultureSelector : this . visual . host . locale , format } ) ; } var formattedValue = customFormatter . format ( rawValue ) ; return formattedValue ; } private GetValueForColumnRowCalculationByName ( row , colDef ) { var model = this . visual . getModel ( ) ; var colNameWithBrackets = colDef . refName ; var rawValue = 0 ; var colIndex = - 1 ; var modelRow = model [ 0 ] ; for ( let m of model ) { if ( row . formula !== null && m . name !== null ) { var iPos = row . formula . toLowerCase ( ) . indexOf ( m . name . toLowerCase ( ) ) ; if ( iPos !== - 1 ) { modelRow = m ; break ; } } } for ( var i = 0 ; i < modelRow . values . length ; i ++ ) { if ( modelRow . values [ i ] . refName === colNameWithBrackets ) { colIndex = i ; break ; } } var retValue = null ; if ( colIndex !== - 1 && typeof ( colIndex ) !== "undefined" ) { retValue = this . GetValueForColumnRowCalculationByIndex ( row , colIndex , colDef , modelRow ) ; } else { retValue = { formattedValue : "(Unknown column)" , rawValue : null } ; } return retValue ; } private GetValueForColumCalculation ( row , col ) { var calculationFormula = col . calculationFormula ; var s = calculationFormula ; var i = 0 ; var result = 0 ; var resultExpression = calculationFormula ; while ( true ) { s = s . trim ( ) ; if ( s . length === 0 || i > 10 ) { break ; } if ( s [ 0 ] === "[" ) { s = "+" + s ; } var i1 = s . indexOf ( "[" , 0 ) ; if ( i1 === - 1 ) { break ; } var i2 = s . indexOf ( "]" , i1 ) ; var name = s . substring ( i1 , i2 + 1 ) ; var calcColDef = col ; var orgRefName = calcColDef . refName ; calcColDef . refName = name ; var columnValue = this . GetValueForColumnRowCalculationByName ( row , calcColDef ) . rawValue ; resultExpression = resultExpression . replace ( name , columnValue ) ; s = s . substr ( i2 + 1 ) ; i ++ ; calcColDef . refName = orgRefName ; } var format = col . format ; if ( Utils . containsValue ( row . format ) ) { format = row . format ; } var evalValue = Calculator . EvalFormula ( resultExpression ) ; var resultFormatted = this . FormatValue ( evalValue , format ) ; return { formattedValue : resultFormatted , rawValue : evalValue } ; } private getTableTotalWidth ( ) { var w = 0 ; var additionalWidth = this . tableDefinition . additionalWidth ; for ( let col of this . tableDefinition . columns ) { w += col . width ; } if ( ! isNaN ( additionalWidth ) ) { w += additionalWidth ; } return w ; } private getStyle ( style ) { if ( typeof ( style ) === "undefined" ) { return "" ; } if ( typeof ( this . tableDefinition . reusableCSS ) === "undefined" ) { return style ; } if ( this . tableDefinition . reusableCSS . length === 0 ) { return style ; } var style2 = style ; for ( let cssItem of this . tableDefinition . reusableCSS ) { style2 = Utils . replace2 ( style2 , cssItem . key , cssItem . value ) ; } return style2 ; } private getStringInside ( startChar , endChar , s , includeContaining ) { var i1 = s . indexOf ( startChar , 0 ) ; var i2 = s . indexOf ( endChar , i1 ) ; if ( i1 === - 1 || i2 === - 1 ) { return null ; } var s2 = s . substring ( i1 + startChar . length , i2 ) ; if ( includeContaining ) { return s . substring ( i1 , i2 + endChar . length ) ; } else { return s . substring ( i1 + startChar . length , i2 ) ; } } private getTitle ( col ) { var model = this . visual . getModel ( ) ; var i1 = col . title . indexOf ( "eval(" , 0 ) ; var i2 = col . title . indexOf ( ")" , i1 ) ; if ( i1 === - 1 || i2 === - 1 ) { return col . title ; } var expressionToEval = col . title . substring ( i1 + 5 , i2 ) ; var colNameWithBrackets = this . getStringInside ( "[" , "]" , expressionToEval , true ) ; if ( colNameWithBrackets === null ) { return col . title ; } var colIndex = null ; for ( var i = 0 ; i < model [ 0 ] . values . length ; i ++ ) { if ( model [ 0 ] . values [ i ] . refName === colNameWithBrackets ) { colIndex = i ; break ; } } if ( colIndex === null ) { return col . title ; } var title = col . title ; title = Utils . replace2 ( title , colNameWithBrackets , model [ 0 ] . values [ colIndex ] . rawValue ) ; i1 = title . indexOf ( "eval(" , 0 ) ; i2 = title . lastIndexOf ( ")" ) ; var v = title . substring ( i1 + 5 , i2 ) ; var vEvaluated = title . substring ( 0 , i1 ) + Calculator . EvalFormula ( v ) + title . substring ( i2 + 1 ) ; return vEvaluated . trim ( ) ; } private appendTextToNode ( targetNode , text ) { var a = text . split ( "<br>" ) ; if ( a . length === 0 ) { targetNode . appendChild ( document . createTextNode ( text ) ) ; } else { for ( var i = 0 ; i < a . length ; i ++ ) { targetNode . appendChild ( document . createTextNode ( a [ i ] ) ) ; if ( i !== a . length - 1 ) { targetNode . appendChild ( document . createElement ( "br" ) ) ; } } } } private htmlGetMasterHeader ( ) { var dTableMasterHeader = document . createElement ( "div" ) ; dTableMasterHeader . className = "div-table-row-masterheader" ; dTableMasterHeader . setAttribute ( "style" , this . tableDefinition . masterHeader . headerStyle ) ; var dTableMasterHeaderContents = document . createElement ( "div" ) ; dTableMasterHeader . appendChild ( dTableMasterHeaderContents ) ; this . appendTextToNode ( dTableMasterHeaderContents , this . tableDefinition . masterHeader . title ) ; return dTableMasterHeader ; } private htmlGetColumnHeader ( column ) { var headerStyle = this . getStyle ( column . headerStyle ) ; var headerTitle = this . getTitle ( column ) ; var dDiv1 = document . createElement ( "div" ) ; dDiv1 . className = "div-table-col-number table-cell-content" ; dDiv1 . setAttribute ( "style" , "max-width:" + column . width + "px;width:" + column . width + "px;min-width:" + column . width + "px;" + headerStyle ) ; var dDiv2 = document . createElement ( "div" ) ; dDiv1 . appendChild ( dDiv2 ) ; dDiv2 . className = "table-cell-content-inner" ; this . appendTextToNode ( dDiv2 , headerTitle ) ; return dDiv1 ; } private htmlGetColumnContent ( rowStyle , cellRowDataStyle , renderValue ) { var dDiv1 = document . createElement ( "div" ) ; dDiv1 . className = "div-table-col-number table-cell-content" ; dDiv1 . setAttribute ( "style" , rowStyle + ";" + cellRowDataStyle ) ; var dDiv2 = document . createElement ( "div" ) ; dDiv1 . appendChild ( dDiv2 ) ; dDiv2 . className = "table-cell-content-inner" ; dDiv2 . appendChild ( document . createTextNode ( renderValue ) ) ; return dDiv1 ; } private clearHtmlElement ( element ) { while ( element . firstChild ) { element . removeChild ( element . firstChild ) ; } } private getCellContents ( col , row , allColumnsAreBlank , cellRowDataStyle ) { var renderValue = "" ; var cellContents = null ; if ( col . type === "Data" ) { var v = this . GetValueForColumnRowCalculationByName ( row , col ) ; allColumnsAreBlank = v . rawValue !== null ? false : allColumnsAreBlank ; if ( isNaN ( Number ( v . rawValue ) ) || v . rawValue === null ) { renderValue = "\u00A0" ; } else { renderValue = v . formattedValue ; } v . formatString = col . format ; cellContents = v ; } else if ( col . type === "RowHeader" ) { renderValue = row . title ; var cellRowHeaderStyle = this . getStyle ( row . cellRowHeaderStyle ) ; cellRowDataStyle = cellRowHeaderStyle ; cellContents = { rawValue : null , formatString : null } ; } else if ( col . type === "Calculation" ) { var calcValue = this . GetValueForColumCalculation ( row , col ) ; renderValue = calcValue . formattedValue ; if ( renderValue . toLowerCase ( ) !== "(blank)" && renderValue . toLowerCase ( ) !== "nan" ) { allColumnsAreBlank = false ; } else { renderValue = "\u00A0" ; } calcValue . formatString = col . format ; cellContents = calcValue ; } else { renderValue = "" ; cellContents = { rawValue : null , formatString : null } ; } cellContents . renderValue = renderValue ; cellContents . cellRowDataStyle = cellRowDataStyle ; cellContents . styleByMeasure = null ; if ( typeof col . styleByMeasure !== 'undefined' && col . styleByMeasure . length > 0 ) { cellContents . styleByMeasure = col . styleByMeasure ; } return cellContents ; } public RenderAllContent ( targetElement , tableDefinitionFromCaller ) { this . tableDefinition = tableDefinitionFromCaller ; this . clearHtmlElement ( targetElement ) ; var model = this . visual . getModel ( ) ; if ( this . tableDefinition === null ) { this . RenderNoContentText ( targetElement ) ; return ; } if ( model . length > 0 && model [ 0 ] . values . length > 1 ) { var hasNonNumeric = false ; for ( var i = 1 ; i < model [ 0 ] . values . length ; i ++ ) { if ( ! model [ 0 ] . values [ i ] . isNumeric ) { hasNonNumeric = true ; } } } var customTableStyle = "" ; if ( typeof this . tableDefinition . masterHeader !== 'undefined' ) { customTableStyle = ";" + this . tableDefinition . masterHeader . borderStyle + ";" ; } var w = this . getTableTotalWidth ( ) ; var dTableWrapper = document . createElement ( "div" ) ; dTableWrapper . className = "tablewrapper" ; var dTable = document . createElement ( "div" ) ; dTable . className = "div-table" ; dTable . setAttribute ( "style" , customTableStyle ) ; dTableWrapper . appendChild ( dTable ) ; var rowStyle = this . getStyle ( this . tableDefinition . headerRow . rowStyle ) ; if ( typeof this . tableDefinition . masterHeader !== 'undefined' ) { dTable . appendChild ( this . htmlGetMasterHeader ( ) ) ; } var dTableRowHeader = document . createElement ( "div" ) ; dTableRowHeader . className = "div-table-row-header" ; dTableRowHeader . setAttribute ( "style" , rowStyle ) ; dTable . appendChild ( dTableRowHeader ) ; for ( var c = 0 ; c < this . tableDefinition . columns . length ; c ++ ) { if ( ! this . tableDefinition . columns [ c ] . hidden ) { dTable . appendChild ( this . htmlGetColumnHeader ( this . tableDefinition . columns [ c ] ) ) ; } } var DisplayAllRows = false ; if ( typeof ( this . tableDefinition . displayAllRows ) !== "undefined" ) { DisplayAllRows = this . tableDefinition . displayAllRows ; } for ( var r = 0 ; r < this . tableDefinition . rows . length ; r ++ ) { var row = this . tableDefinition . rows [ r ] ; var newFormula = "" ; if ( row . formula . indexOf ( "::" ) > - 1 ) { var p = row . formula . indexOf ( "::" ) ; var startRange = row . formula . substring ( 0 , p ) . trim ( ) ; var endRange = row . formula . substring ( p + 2 ) . trim ( ) ; for ( var i = 0 ; i < model . length ; i ++ ) { if ( model [ i ] . name >= startRange && model [ i ] . name <= endRange ) { newFormula += "+" + model [ i ] . name ; } } row . formula = newFormula ; } } var rowWithContentsCounter = 0 ; for ( var r = 0 ; r < this . tableDefinition . rows . length ; r ++ ) { var row = this . tableDefinition . rows [ r ] ; var rowHtml = "" ; var rowStyle = this . getStyle ( row . rowStyle ) ; var dRow = document . createElement ( "div" ) ; dRow . className = "div-table-row" ; dRow . setAttribute ( "style" , rowStyle ) ; var allColumnsAreBlank = true ; var rowCols = [ ] ; for ( var c = 0 ; c < this . tableDefinition . columns . length ; c ++ ) { var col = this . tableDefinition . columns [ c ] ; var colRowStyle = this . getStyle ( col . rowStyle ) ; var rowStyle = "max-width:" + col . width + "px;" + "min-width:" + col . width + "px;" + "width:" + col . width + "px;" + colRowStyle ; var cellRowDataStyle = this . getStyle ( row . cellRowDataStyle ) ; var cellContents = this . getCellContents ( col , row , allColumnsAreBlank , cellRowDataStyle ) ; cellRowDataStyle = cellContents . cellRowDataStyle ; allColumnsAreBlank = cellContents . allColumnsAreBlank ; if ( typeof row . directColumnRef !== 'undefined' ) { for ( var i = 0 ; i < row . directColumnRef . length ; i ++ ) { if ( row . directColumnRef [ i ] . columnRefName === col . refName ) { var replaceWithColumn = row . directColumnRef [ i ] . columnReplaceRefName ; var replaceCol = this . tableDefinition . columns . filter ( a => a . refName === replaceWithColumn ) [ 0 ] ; if ( typeof replaceCol !== 'undefined' ) { cellContents = this . getCellContents ( replaceCol , row , allColumnsAreBlank , cellRowDataStyle ) ; break ; } } } } if ( cellContents . styleByMeasure !== null ) { var colStyle = this . tableDefinition . columns . filter ( a => a . refName === cellContents . styleByMeasure ) [ 0 ] ; var colStyleContents = this . getCellContents ( colStyle , row , allColumnsAreBlank , cellRowDataStyle ) ; rowStyle = rowStyle + ";" + colStyleContents . modelRawValue + ";" ; } rowCols . push ( cellContents ) ; var renderValue = cellContents . renderValue ; var shouldHideValue = false ; if ( typeof row . hideForColumns !== 'undefined' ) { for ( var i = 0 ; i < row . hideForColumns . length ; i ++ ) { if ( row . hideForColumns [ i ] === col . refName ) { shouldHideValue = true ; break ; } } } if ( shouldHideValue ) { renderValue = "\u00A0" ; } if ( row . formula . length === 0 ) { renderValue = "" ; } if ( ! col . hidden ) { dRow . appendChild ( this . htmlGetColumnContent ( rowStyle , cellRowDataStyle , renderValue ) ) ; } } var isCalculatedRow = true ; for ( var i = 0 ; i < model . length ; i ++ ) { if ( model [ i ] . title === row . title ) { isCalculatedRow = false ; } } if ( isCalculatedRow && row . title . length > 0 ) { var newTitle = row . title ; var newName = "[" + newTitle + "]" ; for ( var c = 0 ; c < rowCols . length ; c ++ ) { rowCols [ c ] . displayName = newTitle ; rowCols [ c ] . refName = this . tableDefinition . columns [ c ] . refName ; rowCols [ c ] . formatString = rowCols [ c ] . format ; } var newModelRow = { name : newName , title : newTitle , values : rowCols } ; model . push ( newModelRow ) ; } if ( row . visible ) { if ( ! allColumnsAreBlank || row . formula . length === 0 || DisplayAllRows ) { dTable . appendChild ( dRow ) ; } else { } } if ( row . visible && ! allColumnsAreBlank && row . formula !== "" ) { if ( rowWithContentsCounter % 2 === 1 ) { if ( typeof ( this . tableDefinition . alternatingRowStyle ) !== 'undefined' ) { let currStyle = dRow . getAttribute ( "style" ) ; dRow . setAttribute ( "style" , currStyle + ";" + this . tableDefinition . alternatingRowStyle + ";" ) ; } } rowWithContentsCounter ++ ; } } targetElement . appendChild ( dTableWrapper ) ; } }