UNPKG

coffeescript-ui

Version:
1,242 lines (974 loc) 34 kB
### * coffeescript-ui - Coffeescript User Interface System (CUI) * Copyright (c) 2013 - 2016 Programmfabrik GmbH * MIT Licence * https://github.com/programmfabrik/coffeescript-ui, http://www.coffeescript-ui.org ### class CUI.ListView extends CUI.SimplePane @defaults: row_move_handle_tooltip: "Drag to move row" #Construct a new CUI.ListView. # # @param [Object] options for listview creation # @option options [String] TODO constructor: (opts) -> super(opts) @initListView() initListView: -> @fixedColsCount = @_fixedCols @fixedRowsCount = @_fixedRows @__cols = @_cols.slice(0) if @_colClasses @__colClasses = @_colClasses.slice(0) if @_rowMove CUI.util.assert(not @_rowMovePlaceholder, "new CUI.ListView", "opts.rowMove cannot be used with opts.rowMovePlaceholder", opts: @opts) if @_rowMove or @_rowMovePlaceholder @__cols.splice(0,0, "fixed") if not @__colClasses @__colClasses = [] @__colClasses.splice(0,0, "cui-lv-row-move-handle-column") CUI.util.assert(@fixedColsCount < @__cols.length, "new CUI.ListView", "opts.fixedCols must be less than column count.", opts: @opts) if @_colResize @__colResize = true else if @fixedRowsCount > 0 and @_colResize == undefined @__colResize = true if @__colResize CUI.util.assert(@fixedRowsCount > 0, "new CUI.ListView", "Cannot enable col resize with no fixed rows.", opts: @opts) @__maxCols = [] for col, col_i in @__cols CUI.util.assert(col in ["auto", "maximize", "fixed", "manual"], "new #{@__cls}", "Unknown type of col: \"#{col}\". opts.cols can only contain \"auto\" and \"maximize\" elements.") if col == "maximize" # CUI.util.assert(@_maximize, "new CUI.ListView", "maximized columns can only exist inside an maximized ListView", opts: @opts) CUI.util.assert(col_i >= @fixedColsCount, "new CUI.ListView", "maximized columns can only be in the non-fixed side of the ListView.", opts: @opts) @__maxCols.push(col_i) if @__maximize_horizontal and @__maxCols.length == 0 # auto-max the last column len = @__cols.length - 1 if len >= @fixedColsCount @__maxCols.push(len) @__cols[len] = 'maximize' @rowsCount = 0 @colsCount = @__cols.length @listViewCounter = CUI.ListView.counter++ @__manualColWidths = [] @__colspanRows = {} @colsOrder = [] for col_i in [0...@colsCount] @colsOrder.push(col_i) @rowsOrder = [] @__maxRowIdx = -1 @__resetCellDims() @__cells = [] @__rows = [] @__lvClass = "cui-lv-#{@listViewCounter}" @__deferredRows = [] @__isInDOM = false @__doLayoutBound = => @__doLayout() @addClass("cui-list-view") initOpts: -> super() @addOpts colClasses: check: "Array" cols: mandatory: true check: "Array" fixedCols: default: 0 check: "Integer" fixedRows: default: 0 check: "Integer" # this adds an extra column at the beginning # it also adds a dummy colClasses item if colClasses # are set rowMove: default: false check: Boolean rowMoveFixedRows: default: 0 check: "Integer" # if set, add a rowMovePlaceholder to # all rows rowMovePlaceholder: default: false check: Boolean colResize: check: Boolean selectableRows: check: (v) -> v == false or v == true or v == "multiple" focusable: check: Boolean default: false onRowMove: check: Function onScroll: check: Function header: deprecated: true footer: deprecated: true onSelect: check: Function onDeselect: check: Function readOpts: -> if @opts.header @opts.header_center = @opts.header if @opts.footer @opts.footer_left = @opts.footer super() @__selectableRows = @_selectableRows @ destroy: -> # console.error "#{CUI.util.getObjectClass(@)}.destroy list-view-#{@listViewCounter} called. This is NOT an error." delete(@colsOrder) delete(@rowsOrder) delete(@__fillRowQ3) # @hideWaitBlock() @__isInDOM = null CUI.scheduleCallbackCancel(call: @__doLayoutBound) @listViewTemplate?.destroy() @__layoutIsStopped = false super() @ getListViewClass: -> @__lvClass getGrid: -> @grid hasResizableColumns: -> @__colResize hasMovableRows: -> @_rowMove isInactive: -> !!@__inactive setInactive: (inactive, addClass="inactive") -> @__inactive = !!inactive if @grid if @__inactive CUI.dom.addClass(@grid, addClass) @__inactiveWaitBlock = new CUI.WaitBlock(element: @grid, inactive: true).show() else @__inactiveWaitBlock?.destroy() @__inactiveWaitBlock = null CUI.dom.removeClass(@grid, addClass) @ render: -> CUI.util.assert(not @grid, "ListView.render", "ListView already rendered", opts: @opts) html = [] cls = ["cui-list-view-grid", @__lvClass] if @_fixedCols == 1 and (@_rowMove or @_rowMovePlaceholder) cls.push("cui-list-view-grid-fixed-col-has-only-row-move-handle") if @_rowMovePlaceholder cls.push("cui-list-view-has-row-move-placeholder") if @_rowMove cls.push("cui-list-view-has-row-move") if @__maxCols.length > 0 cls.push("cui-list-view-grid-has-maximized-columns") if @fixedColsCount > 0 cls.push("cui-list-view-grid-has-fixed-cols") if @fixedRowsCount > 0 cls.push("cui-list-view-grid-has-fixed-rows") html.push("<div class=\"") html.push(cls.join(" ")) html.push("\">") html.push("<style></style>") add_quadrant = (qi) => if @__isFocusable() # add tabindex="-1" html.push("<div cui-lv-quadrant=\"#{qi}\" class=\"cui-drag-scroll cui-list-view-grid-quadrant cui-lv-tbody cui-list-view-grid-quadrant-#{qi} #{@__lvClass}-quadrant\">") else html.push("<div cui-lv-quadrant=\"#{qi}\" class=\"cui-drag-scroll cui-list-view-grid-quadrant cui-lv-tbody cui-list-view-grid-quadrant-#{qi} #{@__lvClass}-quadrant\">") if qi in [2, 3] html.push("<div class=\"cui-lv-tr-fill-outer\"><div class=\"cui-lv-tr\">") ft = @__getColsFromAndTo(qi) for col_i in [ft.from..ft.to] by 1 cls = @__getColClass(col_i) html.push("<div class=\"#{cls} cui-lv-td cui-lv-td-fill cui-list-view-grid-fill-col-#{col_i}\"></div>") html.push("</div></div>") html.push("</div>") return if @fixedColsCount > 0 and @fixedRowsCount > 0 html.push("<div class=\"cui-list-view-grid-inner-top\">") add_quadrant(0) add_quadrant(1) html.push("</div>") html.push("<div class=\"cui-list-view-grid-inner-bottom\">") add_quadrant(2) add_quadrant(3) html.push("</div>") else if @fixedColsCount > 0 html.push("<div class=\"cui-list-view-grid-inner-bottom\">") add_quadrant(2) add_quadrant(3) html.push("</div>") else if @fixedRowsCount > 0 html.push("<div class=\"cui-list-view-grid-inner-top\">") add_quadrant(1) html.push("</div>") html.push("<div class=\"cui-list-view-grid-inner-bottom\">") add_quadrant(3) html.push("</div>") else add_quadrant(3) html.push("</div>") outer = @center() outer.innerHTML = html.join("") @grid = outer.firstChild @quadrant = [ CUI.dom.matchSelector(outer, ".cui-list-view-grid-quadrant-0")[0] CUI.dom.matchSelector(outer, ".cui-list-view-grid-quadrant-1")[0] CUI.dom.matchSelector(outer, ".cui-list-view-grid-quadrant-2")[0] CUI.dom.matchSelector(outer, ".cui-list-view-grid-quadrant-3")[0] ] @styleElement = CUI.dom.matchSelector(outer, "style")[0] @__fillRowQ3 = CUI.dom.matchSelector(@grid, ".cui-list-view-grid-fills-3")[0] @__topQuadrants = CUI.dom.matchSelector(outer, ".cui-list-view-grid-inner-top")[0] if (@fixedColsCount == 0 and @fixedRowsCount == 0 ) # we only have Q3 @__bottomQuadrants = @quadrant[3] else @__bottomQuadrants = CUI.dom.matchSelector(outer, ".cui-list-view-grid-inner-bottom")[0] @__fillCells = [] for col in [0..@colsCount-1] by 1 @__fillCells.push(CUI.dom.matchSelector(outer, ".cui-list-view-grid-fill-col-#{col}")[0]) on_scroll = => @__syncScrolling() @_onScroll?() CUI.Events.listen node: @quadrant[3] type: "scroll" call: on_scroll @__currentScroll = top: 0, left: 0 if @hasSelectableRows() selector = "."+@__lvClass+"-quadrant > .cui-lv-tr-outer" CUI.Events.listen type: ["click"] node: @DOM selector: selector call: (ev) => row = CUI.dom.data(ev.getCurrentTarget(), "listViewRow") if not row.isSelectable() return ev.stopImmediatePropagation() @selectRow(ev, row) return if @__isFocusable() selectorFocus = "."+@__lvClass+"-quadrant > .cui-lv-tr-outer:focus" CUI.Events.listen type: ["keydown"] node: @DOM selector: selectorFocus call: (ev) => if ev.getKeyboard() not in ["Return", "Space"] return row = CUI.dom.data(ev.getCurrentTarget(), "listViewRow") if not row.isSelectable() return ev.stopImmediatePropagation() @selectRow(ev, row) return if @quadrant[2] CUI.Events.listen type: "wheel" node: @quadrant[2] call: (ev) => scroll_delta = 100 if ev.wheelDeltaY() > 0 if @quadrant[3].scrollTop == (@quadrant[3].scrollHeight - @quadrant[3].offsetHeight) # at bottom return @quadrant[3].scrollTop += scroll_delta else if ev.wheelDeltaY() < 0 if @quadrant[3].scrollTop == 0 # at top return @quadrant[3].scrollTop -= scroll_delta else return ev.preventDefault() on_scroll() return CUI.Events.listen type: "viewport-resize" node: @grid call: (ev, info) => if not @__hasLayout return @__doLayout(resetRows: !!(info.css_load or info.tab)) return CUI.Events.listen type: "content-resize" node: @DOM call: (ev, info) => if not @__isInDOM return cell = CUI.dom.closest(ev.getNode(), ".cui-lv-td") if not cell return ev.stopPropagation() row = parseInt(cell.getAttribute("row")) col = parseInt(cell.getAttribute("col")) if @fixedColsCount > 0 and CUI.dom.getAttribute(cell.parentNode, "cui-lv-tr-unmeasured") # row has not been measured return @__resetRowDim(row) @__scheduleLayout() return if @isInactive() @setInactive(true) # if @__showWaitBlock # @showWaitBlock() @appendDeferredRows() CUI.dom.waitForDOMInsert(node: @DOM) .done => @__isInDOM = true @__doLayout() @DOM __getScrolling: -> dim = top: @quadrant[3].scrollTop left: @quadrant[3].scrollLeft height: @quadrant[3].scrollHeight dim getScrollingContainer: -> @quadrant[3] __setScrolling: (scroll) -> @quadrant[3].scrollTop = scroll.top @quadrant[3].scrollLeft = scroll.left __syncScrolling: -> @__currentScroll = @__getScrolling() if @fixedColsCount > 0 @quadrant[2].scrollTop = @__currentScroll.top if @fixedRowsCount > 0 @quadrant[1].scrollLeft = @__currentScroll.left if @__fillRowQ3 @__fillRowQ3.style.width = "" @__fillRowQ3.style.width = @__getValue(@__fillRowQ3.scrollWidth) @ __setMargins: -> width = @quadrant[3].offsetWidth - @quadrant[3].clientWidth height = @quadrant[3].offsetHeight - @quadrant[3].clientHeight @quadrant[1]?.style.marginRight = @__getValue(width) @quadrant[2]?.style.marginBottom = @__getValue(height) @ getSelectedRows: -> # console.time "getSelectedRows" sel_rows = [] for row_i in @rowsOrder listViewRow = @getListViewRow(row_i) if listViewRow.isSelected() sel_rows.push(listViewRow) # console.timeEnd "getSelectedRows" sel_rows hasSelectableRows: -> !!@__selectableRows __isFocusable: -> return @_focusable selectRowById: (row_id) -> @selectRow(null, @getListViewRow(row_id), true) selectRowByDisplayIdx: (row_display_idx) -> @selectRowById(@getRowIdx(row_display_idx)) # deselectRow deselects the given row, this # method is here, so it can be overwritten in ListViewTree where # we support different selection groups deselectRow: (ev, row, newRow) -> return row.deselect(ev, newRow) selectRow: (ev, rowChosen, noDeselect=false) -> CUI.util.assert(CUI.util.isNull(rowChosen) or rowChosen instanceof CUI.ListViewRow, "#{@__cls}.setSelectedRow", "Parameter needs to be instance of CUI.ListViewRow.", selectedRow: rowChosen) dfr = new CUI.Deferred() selectRowChosen = => if rowChosen.isSelected() if not noDeselect # this is a "toggle", so if the row is selected, we deselect it # otherwise it is selected. @deselectRow(ev, rowChosen, rowChosen) else dfr.resolve() else rowChosen.select(ev).done(dfr.resolve).fail(dfr.reject) return deselectAllRows = (skipSelf = true) => promises = [] for _row in @getSelectedRows() if rowChosen == _row and skipSelf # we handle this in do_select continue promise = @deselectRow(null, _row, rowChosen) # null is sent as event parameter, to avoids checks. if CUI.util.isPromise(promise) promises.push(promise) CUI.when(promises).done(selectRowChosen).fail(dfr.reject) if @__selectableRows == true # only one row deselectAllRows() else if @__selectableRows == "multiple" # If CTRL key is pressed, then It is allowed to select more rows. if ev?.ctrlKey() selectRowChosen() # If Shift key is pressed then all next or previous rows are selected. else if ev?.shiftKey() and @getSelectedRows().length > 0 selectedRow = @getSelectedRows().pop() idxSelectedRow = selectedRow.getRowIdx() idxClickedRow = rowChosen.getRowIdx() while(idxClickedRow != idxSelectedRow) @getListViewRow(idxClickedRow).select(ev) if idxClickedRow > idxSelectedRow then idxClickedRow-- else idxClickedRow++ else # Otherwise all rows are deselected except for the clicked one. deselectAllRows(false) else selectRowChosen() dfr.promise() getCellByTarget: ($target) -> if CUI.dom.is($target, ".cui-lv-td") cell = col_i: parseInt($target.getAttribute("col")) row_i: parseInt($target.getAttribute("row")) cell.display_col_i = @getDisplayColIdx(cell.col_i) cell.display_row_i = @getDisplayRowIdx(cell.row_i) cell else null getRowMoveTool: (opts = {}) -> new CUI.ListViewRowMove(opts) getListViewRow: (row_i) -> CUI.dom.data(@getRow(row_i)[0], "listViewRow") getDisplayColIdx: (col_i) -> @colsOrder.indexOf(parseInt(col_i)) getDisplayRowIdx: (row_i) -> @rowsOrder.indexOf(parseInt(row_i)) getColIdx: (display_col_i) -> CUI.util.assert(CUI.util.isArray(@colsOrder), "ListView[#{@listViewCounter}].getColIdx", "colsOrder Array is missing", this: @, display_col_i: display_col_i) @colsOrder[display_col_i] getRowIdx: (display_row_i) -> @rowsOrder[display_row_i] moveInOrderArray: (from_i, to_i, array, after) -> display_from_i = array.indexOf(from_i) display_to_i = array.indexOf(to_i) CUI.util.moveInArray(display_from_i, display_to_i, array, after) null moveRow: (from_i, to_i, after=false, trigger_row_moved=true) -> CUI.util.assert(from_i >= @fixedRowsCount and to_i >= @fixedRowsCount, "ListView.moveRow", "from_i and to_i must not be in flexible area of the list view", from_i: from_i, to_i: to_i, fixed_i: @fixedRowsCount) if after func = CUI.dom.insertAfter else func = CUI.dom.insertBefore for row, idx in @getRow(from_i) func(@getRow(to_i)[idx], (row)) display_from_i = @getDisplayRowIdx(from_i) display_to_i = @getDisplayRowIdx(to_i) @moveInOrderArray(from_i, to_i, @rowsOrder, after) if trigger_row_moved @_onRowMove?(display_from_i, display_to_i, after) CUI.Events.trigger type: "row_moved" node: @grid info: from_i: from_i to_i: to_i display_from_i: display_from_i display_to_i: display_to_i after: after @ rowAddClass: (row_i, cls) -> rows = @getRow(row_i) if not rows return for row in rows CUI.dom.addClass(row, cls) @ rowRemoveClass: (row_i, cls) -> rows = @getRow(row_i) if not rows return for row in rows CUI.dom.removeClass(row, cls) @ getColdef: (col_i) -> @__cols[col_i] getColsCount: -> @colsCount resetColWidth: (col_i) -> delete(@__manualColWidths[col_i]) @__resetColWidth(col_i) @__doLayout(resetRows: true) @ setColWidth: (col_i, width) -> @__manualColWidths[col_i] = Math.max(5, width) delete(@__colWidths[col_i]) @__doLayout(resetRows: true) @ getManualColWidth: (col_i) -> @__manualColWidths[col_i] getRowHeight: (row_i) -> @__rows[row_i][0].offsetHeight getColWidth: (col_i) -> @__colWidths[col_i] getCellGridRect: (row_i, col_i) -> cell = @__cells[row_i]?[col_i] if not cell return null grid_rect = CUI.dom.getRect(@grid) pos_grid = top: grid_rect.top left: grid_rect.left dim = CUI.dom.getDimensions(cell) rect = left_abs: dim.clientBoundingRect.left top_abs: dim.clientBoundingRect.top left: dim.clientBoundingRect.left - pos_grid.left top: dim.clientBoundingRect.top - pos_grid.top width: dim.borderBoxWidth height: dim.borderBoxHeight contentWidthAdjust: dim.contentWidthAdjust contentHeightAdjust: dim.contentHeightAdjust rect getRowGridRect: (row_i) -> _rect = width: 0 for row in @__rows[row_i] dim = CUI.dom.getDimensions(row) _rect.width = _rect.width + dim.borderBoxWidth if not _rect.hasOwnProperty("height") _rect.height = dim.borderBoxHeight _rect.top = dim.clientBoundingRect.top if not _rect.hasOwnProperty("left") _rect.left = dim.clientBoundingRect.left grid_rect = CUI.dom.getRect(@grid) _pos_grid = top: grid_rect.top left: grid_rect.left rect = left_abs: _rect.left top_abs: _rect.top left: _rect.left - _pos_grid.left top: _rect.top - _pos_grid.top height: _rect.height rect.width = CUI.dom.width(@getGrid()) return rect # rect = @getCellGridRect(0, row_i) # rect.width = @getGrid().width() # rect appendRow: (row, _defer=!@grid) -> if _defer @__deferRow(row) else @appendRows([row]) prependRow: (row) -> CUI.util.assert(not @isDestroyed(), "ListView.prependRow", "ListView #{@listViewCounter} is already destroyed.") row_i = ++@__maxRowIdx @rowsCount++ @rowsOrder.splice(0, 0, row_i) @__addRow(row_i, row, "prepend") replaceRow: (row_i, row) -> @__addRow(row_i, row, "replace") insertRowAt: (display_row_i, row) -> CUI.util.assert(not @isDestroyed(), "ListView.insertRowAfter", "ListView #{@listViewCounter} is already destroyed.") if display_row_i == @rowsCount or @rowsCount == 0 @appendRow(row) else if display_row_i == 0 @prependRow(row) else @insertRowBefore(@getRowIdx(display_row_i), row) insertRowAfter: (sibling_row_i, row) -> CUI.util.assert(not @isDestroyed(), "ListView.insertRowAfter", "ListView ##{@listViewCounter} is already destroyed.") sibling_display_row_i = @getDisplayRowIdx(sibling_row_i) CUI.util.assert(sibling_display_row_i > -1, "ListView.insertRowAfter", "ListView ##{@listViewCounter}: Row #{sibling_row_i} not found.", row_i: sibling_row_i, row: row, rowsCount: @rowsCount) row_i = ++@__maxRowIdx @rowsCount++ @rowsOrder.splice(sibling_display_row_i+1, 0, row_i) @__addRow(row_i, row, "after", sibling_row_i) insertRowBefore: (sibling_row_i, row) -> CUI.util.assert(not @isDestroyed(), "ListView.insertRowBefore", "ListView ##{@listViewCounter} is already destroyed.") sibling_display_row_i = @getDisplayRowIdx(sibling_row_i) if sibling_display_row_i == 0 return @prependRow(row) before_row_i = @getRowIdx(sibling_display_row_i-1) row_i = ++@__maxRowIdx @rowsCount++ @rowsOrder.splice(sibling_display_row_i, 0, row_i) @__addRow(row_i, row, "after", before_row_i) removeAllRows: -> for row_i in @rowsOrder.slice(0) @removeRow(row_i) @__scheduleLayout() @ removeDeferredRow: (listViewRow) -> count = CUI.util.removeFromArray(listViewRow, @__deferredRows) CUI.util.assert(count == 1, "ListView.removeListViewRow", "row not found", listViewRow: listViewRow) @ removeRow: (row_i) -> CUI.util.assert(row_i != null and row_i >= 0, "ListView.removeRow", "row_i must be >= 0", row_i: row_i) display_row_i = @getDisplayRowIdx(row_i) CUI.util.assert(display_row_i > -1, "ListView.removeRow", "display_row_id not found for row_i", row_i: row_i) @rowsOrder.splice(display_row_i, 1) @rowsCount-- delete(@__colspanRows[row_i]) for row in @getRow(row_i) CUI.dom.remove(row) delete(@__rows[row_i]) @__resetRowDim(row_i) delete(@__cells[row_i]) @__scheduleLayout() @ appendDeferredRows: -> if @__deferredRows.length @appendRows(@__deferredRows) @__deferredRows = [] @ getRow: (row_i) -> @__rows[row_i] getBottom: -> @__bottomQuadrants getTop: -> @__topQuadrants __scheduleLayout: -> # console.error "ListView.__scheduleLayout", @__lvClass if not @__isInDOM return if @layoutIsStopped() @__layoutAfterStart = true return CUI.scheduleCallback(ms: 10, call: @__doLayoutBound) @ layoutIsStopped: -> @__layoutIsStopped stopLayout: -> # console.error @getUniqueId(), "stopping layout..." if @__layoutIsStopped false else @__layoutIsStopped = true true startLayout: -> # console.error @getUniqueId(), "starting layout..." if @__layoutAfterStart @__layoutAfterStart = false @__doLayout() @__layoutIsStopped = null @ __doLayout: (opts={}) -> css = [] add_css = (col_i, width) => css.push("."+@__lvClass+"-cell[col=\""+col_i+"\"] { width: #{width}px !important; flex: 0 0 auto !important;}") has_max_cols = false has_manually_sized_column = false # set width on colspan cells @__colWidths = [] for fc, display_col_i in @__fillCells col_i = @getColIdx(display_col_i) manual_col_width = @__manualColWidths[col_i] if manual_col_width > 0 has_manually_sized_column = true add_css(col_i, manual_col_width) fc.style.setProperty("width", manual_col_width+"px") fc.style.setProperty("flex", "0 0 auto") else if col_i in @__maxCols has_max_cols = true fc.style.removeProperty("width") fc.style.removeProperty("flex") for fc, display_col_i in @__fillCells col_i = @getColIdx(display_col_i) @__colWidths[col_i] = fc.offsetWidth if @__maximize_horizontal if not has_max_cols and has_manually_sized_column CUI.dom.addClass(@grid, "cui-lv--max-last-col") else CUI.dom.removeClass(@grid, "cui-lv--max-last-col") @styleElement.innerHTML = css.join("\n") for row_i, row_info of @__colspanRows for col_i, colspan of row_info cell = CUI.dom.matchSelector(@grid, "."+@__lvClass+"-cell[row=\""+row_i+"\"][col=\""+col_i+"\"]")[0] width = 0 for i in [0...colspan] by 1 # we assume that colspanned columns # are never torn apart, so it is # safe to add "1" here width = width + @__colWidths[parseInt(col_i)+i] dim = CUI.dom.getDimensions(cell) if dim.computedStyle.boxSizing == "border-box" cell.style.setProperty("width", width+"px", "important") else cell.style.setProperty("width", (width - dim.paddingHorizontal - dim.borderHorizontal)+"px", "important") if @fixedColsCount > 0 # find unmeasured rows in Q2 & Q3 and set height # in Q2 for qi in [0, 2] rows = [] if opts.resetRows sel = ".cui-lv-tr-outer" else sel = "[cui-lv-tr-unmeasured=\""+@listViewCounter+"\"]" for row in CUI.dom.matchSelector(@grid, "."+@__lvClass+"-quadrant[cui-lv-quadrant='#{qi}'] > "+sel) rows[parseInt(CUI.dom.getAttribute(row, "row"))] = row CUI.dom.removeAttribute(row, "cui-lv-tr-unmeasured") for row, idx in CUI.dom.matchSelector(@grid, "."+@__lvClass+"-quadrant[cui-lv-quadrant='#{qi+1}'] > "+sel) row_i2 = parseInt(CUI.dom.getAttribute(row, "row")) CUI.dom.prepareSetDimensions(rows[row_i2]) row.__offsetHeight = row.offsetHeight for row, idx in CUI.dom.matchSelector(@grid, "."+@__lvClass+"-quadrant[cui-lv-quadrant='#{qi+1}'] > "+sel) row_i2 = parseInt(CUI.dom.getAttribute(row, "row")) CUI.dom.setDimensions(rows[row_i2], borderBoxHeight: row.__offsetHeight) delete(row.__offsetHeight) CUI.dom.removeAttribute(row, "cui-lv-tr-unmeasured") @__setMargins() @__addRowsOddEvenClasses() @__hasLayout = true @ __addRowsOddEvenClasses: -> if (@rowsCount - @fixedRowsCount)%2 == 0 CUI.dom.addClass(@grid, "cui-list-view-grid-rows-even") CUI.dom.removeClass(@grid, "cui-list-view-grid-rows-odd") else CUI.dom.removeClass(@grid, "cui-list-view-grid-rows-even") CUI.dom.addClass(@grid, "cui-list-view-grid-rows-odd") @ __getValue: (px) -> if not isNaN(parseFloat(px)) px+"px" else if CUI.util.isNull(px) "" else px hideWaitBlock: -> if @__waitBlock @__waitBlock.destroy() delete(@__waitBlock) @ showWaitBlock: -> if @__waitBlock return @ @__waitBlock = new CUI.WaitBlock(element: @DOM) @__waitBlock.show() @ appendRows: (rows) -> CUI.util.assert(not @isDestroyed(), "ListView.appendRow", "ListView #{@listViewCounter} is already destroyed.") for row, idx in rows row_i = ++@__maxRowIdx if idx == 0 start_row_i = row_i @rowsCount++ @rowsOrder.push(row_i) @__addRows(start_row_i, rows) @ __getColsFromAndTo: (qi) -> switch qi when 0, 2 from: 0 to: @fixedColsCount-1 when 1, 3 from: @fixedColsCount to: @colsCount-1 __deferRow: (row) -> @__deferredRows.push(row) __addRow: (row_i, listViewRow=null, mode="append", sibling_row_i=null) -> @__addRows(row_i, [listViewRow], mode, sibling_row_i) # return the two quadrant pairs for the # given row. __getQuadrants: (row_i) -> if @getDisplayRowIdx(row_i) < @fixedRowsCount [0,1] else [2,3] __addRows: (_row_i, listViewRows=[], mode="append", sibling_row_i=null) -> CUI.util.assert(@grid, "ListView.__addRows", "ListView.render has not been called yet.", row_i: _row_i, listView: @) CUI.util.assert(mode in ["append", "prepend", "after", "replace"], "ListView.__addRows", "mode \"#{mode}\" not supported", row_i: _row_i) html = [[],[],[],[]] # if @__hasLayout # @__prepareForLayout() _mode = mode if _mode == "after" and @getDisplayRowIdx(_row_i) == @fixedRowsCount # sibling is in other quadrant mode = "prepend" if mode in ["replace", "after"] switch mode when "replace" CUI.util.assert(listViewRows.length == 1, "ListView.__addRows", "Can only use mode \"#{mode}\" on one row", listViewRows: listViewRows) row_i = _row_i @__resetRowDim(row_i) when "after" row_i = sibling_row_i anchor_row = @__rows[row_i] CUI.util.assert(anchor_row.length >= 1, "ListView.__addRows", "anchor row #{row_i} for mode #{mode} not found.", rows: @__rows, row_i: row_i, mode: _mode, mode_used: mode) # prepare html for all rows for row_i in [_row_i..row_i+listViewRows.length-1] by 1 @__cells[row_i] = [] @__rows[row_i] = [] for qi in @__getQuadrants(row_i) ft = @__getColsFromAndTo(qi) if ft.to < ft.from continue tabindex = if @__isFocusable() then "tabindex=\"1\"" else "" unmeasuredAttribute = if @fixedColsCount > 0 then "cui-lv-tr-unmeasured=\"#{@listViewCounter}\"" else "" html[qi].push("<div class=\"cui-lv-tr-outer\" #{tabindex} #{unmeasuredAttribute} row=\"#{row_i}\"><div class=\"cui-lv-tr\">") for display_col_i in [ft.from..ft.to] by 1 col_i = @getColIdx(display_col_i) [width, maxi] = @__getColWidth(row_i, col_i) cls = @__getColClass(col_i) html[qi].push("<div class=\"cui-lv-td #{cls} #{@__lvClass}-cell\" col=\"#{col_i}\" row=\"#{row_i}\"></div>") html[qi].push("</div></div>") find_cells_and_rows = (top) => # find rows and cells in newly prepared html _cells = CUI.dom.matchSelector(top, ".cui-lv-td") for cell in _cells _col = parseInt(cell.getAttribute("col")) _row = parseInt(cell.getAttribute("row")) @__cells[_row][_col] = cell _rows = CUI.dom.matchSelector(top, ".cui-lv-tr-outer") for row in _rows row_i = parseInt(row.getAttribute("row")) @__rows[row_i].push(row) return anchor_row_idx = 0 for qi in [0..3] if html[qi].length == 0 continue outer = document.createElement("div") outer.innerHTML = html[qi].join("") find_cells_and_rows(outer) if mode == "append" if qi in [2, 3] # the last row is the fill row, make sure to # insert our node before while node = outer.firstChild @quadrant[qi].insertBefore(node, @quadrant[qi].lastChild) else while node = outer.firstChild @quadrant[qi].appendChild(node) continue if mode == "prepend" while node = outer.lastChild @quadrant[qi].insertBefore(node, @quadrant[qi].firstChild) continue row = anchor_row[anchor_row_idx] anchor_row_idx++ if mode == "after" while node = outer.lastChild CUI.dom.insertAfter(row, node) continue if mode == "replace" node = outer.firstChild CUI.dom.replaceWith(row, node) # check for overflow in fixed qudrant if @fixedRowsCount > 0 fixedRows = @quadrant[1].childNodes if @fixedRowsCount < fixedRows.length if @fixedColsCount > 0 _qi_s = [0,1] else _qi_s = [1] for i in [0...fixedRows.length-@fixedRowsCount] by 1 for _qi in _qi_s if @quadrant[_qi+2].firstChild @quadrant[_qi+2].insertBefore(@quadrant[_qi].lastChild, @quadrant[_qi+2].firstChild) else @quadrant[_qi+2].appendChild(@quadrant[_qi].lastChild) for listViewRow, idx in listViewRows if CUI.util.isPromise(listViewRow) do (idx) => listViewRow.done (_listViewRow) => @__appendCells(_listViewRow, _row_i+idx) else @__appendCells(listViewRow, _row_i+idx) @__scheduleLayout() @ __appendCells: (listViewRow, row_i) -> CUI.util.assert(listViewRow instanceof CUI.ListViewRow, "ListView.addRow", "listViewRow needs to be instance of ListViewRow or Deferred which returns a ListViewRow", listViewRow: listViewRow) listViewRow.setRowIdx(row_i).setListView(@) for row in @__rows[row_i] CUI.dom.data(row, "listViewRow", listViewRow) listViewRow.addClass((listViewRow.getClass() or "")+" "+CUI.util.toDash(CUI.util.getObjectClass(listViewRow))) if @_rowMove if @getDisplayRowIdx(row_i) >= @fixedRowsCount + @_rowMoveFixedRows if listViewRow.getColumns()[0] not instanceof CUI.ListViewColumnRowMoveHandle listViewRow.prependColumn(new CUI.ListViewColumnRowMoveHandle()) else if listViewRow.getColumns()[0] not instanceof CUI.ListViewColumnRowMoveHandlePlaceholder listViewRow.prependColumn(new CUI.ListViewColumnRowMoveHandlePlaceholder()) else if @_rowMovePlaceholder if listViewRow.getColumns()[0] not instanceof CUI.ListViewColumnRowMoveHandlePlaceholder listViewRow.prependColumn(new CUI.ListViewColumnRowMoveHandlePlaceholder()) _cols = listViewRow.getColumns() colspan_offset = 0 for col, col_i in _cols node = col.render() cell = @__cells[row_i][col_i+colspan_offset] CUI.util.assert(cell, "ListView.__appendCells", "Cell not found: row: "+row_i+" column: "+(col_i+colspan_offset+1)+". colsCount: "+@colsCount, row: listViewRow) if not CUI.util.isNull(node) CUI.dom.append(cell, node) col.setColumnIdx(col_i) col.setElement(cell) colspan = col.getColspan() CUI.util.assert(col_i+colspan_offset+colspan-1 < @colsCount, "ListView.__appendCells", "Colspan #{colspan} exceeds cols count #{@colsCount}, unable to append cell.", row_i: row_i, col_i: col_i, colspan_offset: colspan_offset, colspan: colspan, column: col, row: listViewRow, ListView: @) if colspan > 1 cell.setAttribute("colspan", colspan) # we remember the colspan here for the addCss # class, its important to do this before we change # colspan_offset if not @__colspanRows[row_i] @__colspanRows[row_i] = {} @__colspanRows[row_i][col_i+colspan_offset] = colspan # delete the real column ids which are supposed # to hide to the right of us # if they are not in order, this is not our problem # at this point for i in [1...colspan] CUI.dom.remove(@__cells[row_i][col_i+colspan_offset+1]) delete(@__cells[row_i][col_i+colspan_offset+1]) colspan_offset++ CUI.util.assert(_cols.length+colspan_offset <= @colsCount, "ListView.addRow", "ListViewRow provided more columns (#{_cols.length+colspan_offset}) than colsCount (#{@colsCount}) is set to", colsCount: @colsCount, cols: _cols) listViewRow.addedToListView(@__rows[row_i]) return __getColClass: (col_i) -> col_cls = @__colClasses?[col_i] cls = [] if CUI.util.isArray(col_cls) cls.push.apply(cls, col_cls) else if not CUI.util.isEmpty(col_cls) cls.push(col_cls) if col_i in @__maxCols cls.push("cui-lv-td-max") cls.join(" ") __resetRowDim: (row_i) -> delete(@__cellDims[row_i]) if @fixedColsCount > 0 and @__rows[row_i] for row in @__rows[row_i] CUI.dom.setAttribute(row, "cui-lv-tr-unmeasured", @listViewCounter) for display_col_i in [0..@colsCount-1] col_i = @getColIdx(display_col_i) @__resetCellStyle(row_i, col_i) @ __resetCellStyle: (row_i, col_i) -> cell = @__cells[row_i]?[col_i] if cell CUI.dom.setStyleOne(cell, "cssText", "") cell __resetColWidth: (col_i) -> for dim in @__cellDims if dim delete(dim[col_i]) for display_row_i in [0..@rowsCount-1] row_i = @getRowIdx(display_row_i) @__resetCellStyle(row_i, col_i) @__fillCells[col_i].style.cssText = "" @ __resetCellDims: (col_i) -> @__cellDims = [] @__colWidths = [] __isMaximizedCol: (col_i) -> col_i in @__maxCols and not @__manualColWidths.hasOwnProperty(col_i) @counter: 0 # use row_i == null to not check for colspan __getColWidth: (row_i, col_i) -> colspan = @__colspanRows[row_i]?[col_i] if colspan > 1 accWidth = 0 maxi = false for _col_i in [col_i..col_i+colspan-1] by 1 if @__isMaximizedCol(_col_i) maxi = true accWidth += @__colWidths[_col_i] [accWidth, maxi] else [@__colWidths[col_i], @__isMaximizedCol(col_i)]