ax5ui-grid
Version:
A grid plugin that works with Bootstrap & jQuery
1,045 lines (959 loc) • 330 kB
JavaScript
"use strict";
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
/*
* Copyright (c) 2016. tom@axisj.com
* - github.com/thomasjang
* - www.axisj.com
*/
// ax5.ui.grid
(function () {
var UI = ax5.ui;
var U = ax5.util;
var GRID = void 0;
UI.addClass({
className: "grid"
}, function () {
/**
* @class ax5grid
* @classdesc
* @author tom@axisj.com
* @example
* ```
* var myGrid = new ax5.ui.grid();
* ```
*/
return function () {
var self = this,
cfg = void 0,
ctrlKeys = {
"33": "KEY_PAGEUP",
"34": "KEY_PAGEDOWN",
"35": "KEY_END",
"36": "KEY_HOME",
"37": "KEY_LEFT",
"38": "KEY_UP",
"39": "KEY_RIGHT",
"40": "KEY_DOWN"
};
this.instanceId = ax5.getGuid();
this.config = {
theme: 'default',
animateTime: 250,
debounceTime: 250,
appendDebouncer: null,
appendDebounceTimes: 0,
appendProgressIcon: '...',
appendProgress: false,
// 틀고정 속성
frozenColumnIndex: 0,
frozenRowIndex: 0,
showLineNumber: false,
showRowSelector: false,
multipleSelect: true,
virtualScrollY: true,
virtualScrollX: true,
headerSelect: true,
// 스크롤될 때 body 페인팅 딜레이를 주어 성능이 좋은 않은 브라우저에서 반응을 빠르게 할 때 사용하는 옵션들
virtualScrollYCountMargin: 0,
virtualScrollAccelerated: true,
virtualScrollAcceleratedDelayTime: 10,
height: 0,
columnMinWidth: 100,
lineNumberColumnWidth: 30,
rowSelectorColumnWidth: 26,
sortable: undefined,
remoteSort: false,
header: {
display: true,
align: false,
columnHeight: 26,
columnPadding: 3,
columnBorderWidth: 1,
selector: true
},
body: {
align: false,
columnHeight: 26,
columnPadding: 3,
columnBorderWidth: 1,
grouping: false,
mergeCells: false
},
rightSum: false,
footSum: false,
page: {
height: 25,
display: true,
statusDisplay: true,
navigationItemCount: 5
},
scroller: {
size: 15,
barMinSize: 15,
trackPadding: 4
},
columnKeys: {
selected: '__selected__',
modified: '__modified__',
deleted: '__deleted__',
disableSelection: '__disable_selection__'
},
tree: {
use: false,
hashDigit: 8,
indentWidth: 10,
arrowWidth: 15,
iconWidth: 18,
icons: {
openedArrow: '▾',
collapsedArrow: '▸',
groupIcon: '⊚',
collapsedGroupIcon: '⊚',
itemIcon: '⊙'
},
columnKeys: {
parentKey: "pid",
selfKey: "id",
collapse: "collapse",
hidden: "hidden",
parentHash: "__hp__",
selfHash: "__hs__",
children: "__children__",
depth: "__depth__"
}
}
};
this.xvar = {
bodyTrHeight: 0, // 한줄의 높이
scrollContentWidth: 0, // 스크롤 될 내용물의 너비 (스크롤 될 내용물 : panel['body-scroll'] 안에 컬럼이 있는)
scrollContentHeight: 0, // 스크롤 된 내용물의 높이
scrollTimer: null
};
// 그리드 데이터셋
this.columns = []; // config.columns에서 복제된 오브젝트
this.colGroup = []; // columns를 table태그로 출력하기 좋게 변환한 오브젝트
this.footSumColumns = [];
this.bodyGrouping = {};
this.list = []; // 그리드의 데이터
this.proxyList = null; // 그리드 데이터의 대리자
this.page = null; // 그리드의 페이지 정보
this.selectedDataIndexs = [];
this.deletedList = [];
this.sortInfo = {}; // 그리드의 헤더 정렬 정보
this.focusedColumn = {}; // 그리드 바디의 포커스된 셀 정보
this.selectedColumn = {}; // 그리드 바디의 선택된 셀 정보
this.isInlineEditing = false;
this.inlineEditing = {};
this.listIndexMap = {}; // tree데이터 사용시 데이터 인덱싱 맵
this.contextMenu = null; // contentMenu 의 인스턴스
// header
this.headerTable = {};
this.leftHeaderData = {};
this.headerData = {};
this.rightHeaderData = {};
// body
this.bodyRowTable = {};
this.leftBodyRowData = {};
this.bodyRowData = {};
this.rightBodyRowData = {};
this.bodyRowMap = {};
this.bodyGroupingTable = {};
this.leftBodyGroupingData = {};
this.bodyGroupingData = {};
this.rightBodyGroupingData = {};
this.bodyGroupingMap = {};
// footSum
this.footSumTable = {}; // footSum의 출력레이아웃
this.leftFootSumData = {}; // frozenColumnIndex 를 기준으로 나누어진 출력 레이아웃 왼쪽
this.footSumData = {}; // frozenColumnIndex 를 기준으로 나누어진 출력 레이아웃 오른쪽
this.needToPaintSum = true; // 데이터 셋이 변경되어 summary 변경 필요여부
cfg = this.config;
var initGrid = function initGrid() {
// 그리드 템플릿에 전달하고자 하는 데이터를 정리합시다.
var data = {
instanceId: this.id
};
this.$target.html(GRID.tmpl.get("main", data));
// 그리드 패널 프레임의 각 엘리먼트를 캐쉬합시다.
this.$ = {
"container": {
"hidden": this.$target.find('[data-ax5grid-container="hidden"]'),
"root": this.$target.find('[data-ax5grid-container="root"]'),
"header": this.$target.find('[data-ax5grid-container="header"]'),
"body": this.$target.find('[data-ax5grid-container="body"]'),
"page": this.$target.find('[data-ax5grid-container="page"]'),
"scroller": this.$target.find('[data-ax5grid-container="scroller"]')
},
"panel": {
"aside-header": this.$target.find('[data-ax5grid-panel="aside-header"]'),
"left-header": this.$target.find('[data-ax5grid-panel="left-header"]'),
"header": this.$target.find('[data-ax5grid-panel="header"]'),
"header-scroll": this.$target.find('[data-ax5grid-panel-scroll="header"]'),
"right-header": this.$target.find('[data-ax5grid-panel="right-header"]'),
"top-aside-body": this.$target.find('[data-ax5grid-panel="top-aside-body"]'),
"top-left-body": this.$target.find('[data-ax5grid-panel="top-left-body"]'),
"top-body": this.$target.find('[data-ax5grid-panel="top-body"]'),
"top-body-scroll": this.$target.find('[data-ax5grid-panel-scroll="top-body"]'),
"top-right-body": this.$target.find('[data-ax5grid-panel="top-right-body"]'),
"aside-body": this.$target.find('[data-ax5grid-panel="aside-body"]'),
"aside-body-scroll": this.$target.find('[data-ax5grid-panel-scroll="aside-body"]'),
"left-body": this.$target.find('[data-ax5grid-panel="left-body"]'),
"left-body-scroll": this.$target.find('[data-ax5grid-panel-scroll="left-body"]'),
"body": this.$target.find('[data-ax5grid-panel="body"]'),
"body-scroll": this.$target.find('[data-ax5grid-panel-scroll="body"]'),
"right-body": this.$target.find('[data-ax5grid-panel="right-body"]'),
"right-body-scroll": this.$target.find('[data-ax5grid-panel-scroll="right-body"]'),
"bottom-aside-body": this.$target.find('[data-ax5grid-panel="bottom-aside-body"]'),
"bottom-left-body": this.$target.find('[data-ax5grid-panel="bottom-left-body"]'),
"bottom-body": this.$target.find('[data-ax5grid-panel="bottom-body"]'),
"bottom-body-scroll": this.$target.find('[data-ax5grid-panel-scroll="bottom-body"]'),
"bottom-right-body": this.$target.find('[data-ax5grid-panel="bottom-right-body"]')
},
"livePanelKeys": [], // 현재 사용중인 패널들 (grid-body repaint에서 수집하여 처리)
"scroller": {
"vertical": this.$target.find('[data-ax5grid-scroller="vertical"]'),
"vertical-bar": this.$target.find('[data-ax5grid-scroller="vertical-bar"]'),
"horizontal": this.$target.find('[data-ax5grid-scroller="horizontal"]'),
"horizontal-bar": this.$target.find('[data-ax5grid-scroller="horizontal-bar"]'),
"corner": this.$target.find('[data-ax5grid-scroller="corner"]')
},
"page": {
"navigation": this.$target.find('[data-ax5grid-page="navigation"]'),
"status": this.$target.find('[data-ax5grid-page="status"]')
},
"form": {
"clipboard": this.$target.find('[data-ax5grid-form="clipboard"]')
},
"resizer": {
"vertical": this.$target.find('[data-ax5grid-resizer="vertical"]'),
"horizontal": this.$target.find('[data-ax5grid-resizer="horizontal"]')
}
};
this.$["container"]["root"].css({ height: this.config.height || this.config._height });
return this;
};
var initColumns = function initColumns(_columns) {
if (!U.isArray(_columns)) _columns = [];
this.columns = U.deepCopy(_columns);
this.headerTable = GRID.util.makeHeaderTable.call(this, this.columns);
this.xvar.frozenColumnIndex = cfg.frozenColumnIndex || 0;
this.bodyRowTable = GRID.util.makeBodyRowTable.call(this, this.columns);
this.bodyRowMap = GRID.util.makeBodyRowMap.call(this, this.bodyRowTable);
// 바디에 표현될 한줄의 높이를 계산합니다.
this.xvar.bodyTrHeight = this.bodyRowTable.rows.length * this.config.body.columnHeight;
var colGroupMap = {};
for (var r = 0, rl = this.headerTable.rows.length; r < rl; r++) {
var row = this.headerTable.rows[r];
for (var c = 0, cl = row.cols.length; c < cl; c++) {
colGroupMap[row.cols[c].colIndex] = jQuery.extend({}, row.cols[c]);
}
}
this.colGroup = [];
for (var k in colGroupMap) {
this.colGroup.push(colGroupMap[k]);
}
return this;
};
var onResetColumns = function onResetColumns() {
initColumns.call(this, this.config.columns);
resetColGroupWidth.call(this);
if (this.config.footSum) {
initFootSum.call(this, this.config.footSum);
this.needToPaintSum = true;
}
if (this.config.body.grouping) initBodyGroup.call(this, this.config.body.grouping);
alignGrid.call(this, true);
GRID.header.repaint.call(this, true);
GRID.body.repaint.call(this, true);
GRID.scroller.resize.call(this);
};
var resetColGroupWidth = function resetColGroupWidth() {
/// !! 그리드 target의 크기가 변경되면 이 함수를 호출하려 this.colGroup의 _width 값을 재 계산 하여야 함. [tom]
var CT_WIDTH = this.$["container"]["root"].width() - function () {
var width = 0;
if (cfg.showLineNumber) width += cfg.lineNumberColumnWidth;
if (cfg.showRowSelector) width += cfg.rowSelectorColumnWidth;
width += cfg.scroller.size;
return width;
}(),
totalWidth = 0,
computedWidth = void 0,
autoWidthColgroupIndexs = [],
colGroup = this.colGroup,
i = void 0,
l = void 0;
for (i = 0, l = colGroup.length; i < l; i++) {
if (U.isNumber(colGroup[i].width)) {
totalWidth += colGroup[i]._width = colGroup[i].width;
} else if (colGroup[i].width === "*") {
autoWidthColgroupIndexs.push(i);
} else if (U.right(colGroup[i].width, 1) === "%") {
totalWidth += colGroup[i]._width = CT_WIDTH * U.left(colGroup[i].width, "%") / 100;
}
}
if (autoWidthColgroupIndexs.length > 0) {
computedWidth = (CT_WIDTH - totalWidth) / autoWidthColgroupIndexs.length;
for (i = 0, l = autoWidthColgroupIndexs.length; i < l; i++) {
colGroup[autoWidthColgroupIndexs[i]]._width = computedWidth;
}
}
};
var initFootSum = function initFootSum(_footSum) {
if (U.isArray(_footSum)) {
this.footSumTable = GRID.util.makeFootSumTable.call(this, this.footSumColumns = _footSum);
} else {
this.footSumColumns = [];
this.footSumTable = {};
}
};
var initBodyGroup = function initBodyGroup(_grouping) {
var grouping = jQuery.extend({}, _grouping);
if ("by" in grouping && "columns" in grouping) {
this.bodyGrouping = {
by: grouping.by,
columns: grouping.columns
};
this.bodyGroupingTable = GRID.util.makeBodyGroupingTable.call(this, this.bodyGrouping.columns);
this.sortInfo = function () {
var sortInfo = {};
for (var k = 0, kl = this.bodyGrouping.by.length; k < kl; k++) {
sortInfo[this.bodyGrouping.by[k]] = {
orderBy: "asc",
seq: k,
fixed: true
};
for (var c = 0, cl = this.colGroup.length; c < cl; c++) {
if (this.colGroup[c].key === this.bodyGrouping.by[k]) {
this.colGroup[c].sort = "asc";
this.colGroup[c].sortFixed = true;
}
}
}
return sortInfo;
}.call(this);
} else {
cfg.body.grouping = false;
}
};
var alignGrid = function alignGrid(_isFirst) {
var list = this.proxyList ? this.proxyList : this.list;
// 대상이 크기가 컬럼의 최소 크기 보다 작업 금지
if (Math.min(this.$target.innerWidth(), this.$target.innerHeight()) < 5) {
return false;
}
if (!this.config.height) {
this.$["container"]["root"].css({ height: this.config._height = this.$target.height() });
}
var CT_WIDTH = this.$["container"]["root"].width(),
CT_HEIGHT = this.$["container"]["root"].height(),
CT_INNER_WIDTH = CT_WIDTH,
CT_INNER_HEIGHT = CT_HEIGHT,
asidePanelWidth = cfg.asidePanelWidth = function () {
var width = 0;
if (cfg.showLineNumber) width += cfg.lineNumberColumnWidth;
if (cfg.showRowSelector) width += cfg.rowSelectorColumnWidth;
return width;
}(),
frozenPanelWidth = cfg.frozenPanelWidth = function (colGroup, endIndex) {
var width = 0;
for (var i = 0, l = endIndex; i < l; i++) {
width += colGroup[i]._width;
}
return width;
}(this.colGroup, cfg.frozenColumnIndex),
verticalScrollerWidth = void 0,
horizontalScrollerHeight = void 0,
bodyHeight = void 0;
// todo : 우측 함계컬럼 너비 계산
var rightPanelWidth = 0,
frozenRowHeight = function (bodyTrHeight) {
return cfg.frozenRowIndex * bodyTrHeight;
}(this.xvar.bodyTrHeight),
footSumHeight = function (bodyTrHeight) {
return this.footSumColumns.length * bodyTrHeight;
}.call(this, this.xvar.bodyTrHeight),
headerHeight = cfg.header.display ? this.headerTable.rows.length * cfg.header.columnHeight : 0,
pageHeight = cfg.page.display ? cfg.page.height : 0;
(function () {
verticalScrollerWidth = CT_HEIGHT - headerHeight - pageHeight - footSumHeight < list.length * this.xvar.bodyTrHeight ? this.config.scroller.size : 0;
// 남은 너비가 colGroup의 너비보다 넓을때. 수평 스크롤 활성화.
horizontalScrollerHeight = function () {
var totalColGroupWidth = 0;
// aside 빼고 너비
// 수직 스크롤이 있으면 또 빼고 비교
var bodyWidth = CT_WIDTH - asidePanelWidth - verticalScrollerWidth;
for (var i = 0, l = this.colGroup.length; i < l; i++) {
totalColGroupWidth += this.colGroup[i]._width;
}
return totalColGroupWidth > bodyWidth ? this.config.scroller.size : 0;
}.call(this);
if (horizontalScrollerHeight > 0) {
verticalScrollerWidth = CT_HEIGHT - headerHeight - pageHeight - footSumHeight - horizontalScrollerHeight < list.length * this.xvar.bodyTrHeight ? this.config.scroller.size : 0;
}
}).call(this);
// 수평 너비 결정
CT_INNER_WIDTH = CT_WIDTH - verticalScrollerWidth;
// 수직 스크롤러의 높이 결정.
CT_INNER_HEIGHT = CT_HEIGHT - pageHeight - horizontalScrollerHeight;
bodyHeight = CT_INNER_HEIGHT - headerHeight;
var panelDisplayProcess = function panelDisplayProcess(panel, vPosition, hPosition, containerType) {
var css = {},
isHide = false;
switch (hPosition) {
case "aside":
if (asidePanelWidth === 0) {
isHide = true;
} else {
css["left"] = 0;
css["width"] = asidePanelWidth;
}
break;
case "left":
if (cfg.frozenColumnIndex === 0) {
isHide = true;
} else {
css["left"] = asidePanelWidth;
css["width"] = frozenPanelWidth;
}
break;
case "right":
if (!cfg.rightSum) {
isHide = true;
} else {}
break;
default:
if (containerType !== "page") {
if (cfg.frozenColumnIndex === 0) {
css["left"] = asidePanelWidth;
} else {
css["left"] = frozenPanelWidth + asidePanelWidth;
}
css["width"] = CT_INNER_WIDTH - asidePanelWidth - frozenPanelWidth - rightPanelWidth;
}
break;
}
if (isHide) {
panel.hide();
// 프로세스 중지
return this;
}
if (containerType === "body") {
switch (vPosition) {
case "top":
if (cfg.frozenRowIndex == 0) {
isHide = true;
} else {
css["top"] = 0;
css["height"] = frozenRowHeight;
}
break;
case "bottom":
if (!cfg.footSum) {
isHide = true;
} else {
css["top"] = bodyHeight - footSumHeight;
css["height"] = footSumHeight; // footSum height
}
break;
default:
css["top"] = frozenRowHeight;
css["height"] = bodyHeight - frozenRowHeight - footSumHeight;
break;
}
} else if (containerType === "header") {
css["height"] = headerHeight;
} else if (containerType === "page") {
if (pageHeight == 0) {
isHide = true;
} else {
css["height"] = pageHeight;
}
}
if (isHide) {
panel.hide();
// 프로세스 중지
return this;
}
panel.show().css(css);
return this;
};
var scrollerDisplayProcess = function scrollerDisplayProcess(panel, scrollerWidth, scrollerHeight, containerType) {
var css = {},
isHide = false;
switch (containerType) {
case "vertical":
if (scrollerWidth > 0) {
css["width"] = scrollerWidth;
css["height"] = CT_INNER_HEIGHT;
css["bottom"] = scrollerHeight + pageHeight;
} else {
isHide = true;
}
break;
case "horizontal":
if (scrollerHeight > 0) {
css["width"] = CT_INNER_WIDTH;
css["height"] = scrollerHeight;
css["right"] = scrollerWidth;
css["bottom"] = pageHeight;
} else {
isHide = true;
}
break;
case "corner":
if (scrollerWidth > 0 && scrollerHeight > 0) {
css["width"] = scrollerWidth;
css["height"] = scrollerHeight;
css["bottom"] = pageHeight;
} else {
isHide = true;
}
break;
}
if (isHide) {
panel.hide();
// 프로세스 중지
return this;
}
panel.show().css(css);
};
this.$["container"]["header"].css({ height: headerHeight });
this.$["container"]["body"].css({ height: bodyHeight });
// 각 패널들의 크기 표시여부를 결정합니다
panelDisplayProcess.call(this, this.$["panel"]["aside-header"], "", "aside", "header");
panelDisplayProcess.call(this, this.$["panel"]["left-header"], "", "left", "header");
panelDisplayProcess.call(this, this.$["panel"]["header"], "", "", "header");
panelDisplayProcess.call(this, this.$["panel"]["right-header"], "", "right", "header");
panelDisplayProcess.call(this, this.$["panel"]["top-aside-body"], "top", "aside", "body");
panelDisplayProcess.call(this, this.$["panel"]["top-left-body"], "top", "left", "body");
panelDisplayProcess.call(this, this.$["panel"]["top-body"], "top", "", "body");
panelDisplayProcess.call(this, this.$["panel"]["top-right-body"], "top", "right", "body");
panelDisplayProcess.call(this, this.$["panel"]["aside-body"], "", "aside", "body");
panelDisplayProcess.call(this, this.$["panel"]["left-body"], "", "left", "body");
panelDisplayProcess.call(this, this.$["panel"]["body"], "", "", "body");
panelDisplayProcess.call(this, this.$["panel"]["right-body"], "", "right", "body");
panelDisplayProcess.call(this, this.$["panel"]["bottom-aside-body"], "bottom", "aside", "body");
panelDisplayProcess.call(this, this.$["panel"]["bottom-left-body"], "bottom", "left", "body");
panelDisplayProcess.call(this, this.$["panel"]["bottom-body"], "bottom", "", "body");
panelDisplayProcess.call(this, this.$["panel"]["bottom-right-body"], "bottom", "right", "body");
scrollerDisplayProcess.call(this, this.$["scroller"]["vertical"], verticalScrollerWidth, horizontalScrollerHeight, "vertical");
scrollerDisplayProcess.call(this, this.$["scroller"]["horizontal"], verticalScrollerWidth, horizontalScrollerHeight, "horizontal");
scrollerDisplayProcess.call(this, this.$["scroller"]["corner"], verticalScrollerWidth, horizontalScrollerHeight, "corner");
panelDisplayProcess.call(this, this.$["container"]["page"], "", "", "page");
// 각 패널의 사이즈 결정
/// 다른 패널의 사이즈 정보가 필요한 경우 여기서 정의해주고 사용함.
this.xvar.bodyHeight = this.$.panel["body"].height();
this.xvar.bodyWidth = this.$.panel["body"].width();
// scrollContentWidth 는 grid-header repaint에서 결정합니다. 까먹지 맙시다. > this.xvar.scrollContentWidth
return true;
};
var sortColumns = function sortColumns(_sortInfo) {
GRID.header.repaint.call(this);
if (U.isFunction(this.config.remoteSort)) {
var that = { sortInfo: [] };
for (var k in _sortInfo) {
that.sortInfo.push({
key: k,
orderBy: _sortInfo[k].orderBy,
seq: _sortInfo[k].seq
});
}
that.sortInfo.sort(function (a, b) {
return a.seq > b.seq;
});
this.config.remoteSort.call(that, that);
} else {
if (this.config.body.grouping) {
this.list = GRID.data.initData.call(this, GRID.data.sort.call(this, _sortInfo, GRID.data.clearGroupingData.call(this, this.list)));
} else {
this.list = GRID.data.sort.call(this, _sortInfo, GRID.data.clearGroupingData.call(this, this.list), { resetLineNumber: true });
}
GRID.body.repaint.call(this, true);
GRID.scroller.resize.call(this);
}
};
/// private end
/**
/**
* Preferences of grid UI
* @method ax5grid.setConfig
* @param {Object} _config - 클래스 속성값
* @param {Element} _config.target
* @param {Number} [_config.frozenColumnIndex=0]
* @param {Number} [_config.frozenRowIndex=0]
* @param {Boolean} [_config.showLineNumber=false]
* @param {Boolean} [_config.showRowSelector=false]
* @param {Boolean} [_config.multipleSelect=true]
* @param {Number} [_config.columnMinWidth=100]
* @param {Number} [_config.lineNumberColumnWidth=30]
* @param {Number} [_config.rowSelectorColumnWidth=25]
* @param {Boolean} [_config.sortable=false]
* @param {Boolean} [_config.multiSort=false]
* @param {Function} [_config.remoteSort=false]
* @param {Boolean} [_config.virtualScrollY=true] - 세로축 가상스크롤 처리여부
* @param {Boolean} [_config.virtualScrollX=true] - 가로축 가상스크롤 처리여부
* @param {Object} [_config.header]
* @param {Object} [_config.header.selector=true] - 헤더 checkbox 표시여부
* @param {String} [_config.header.align]
* @param {Number} [_config.header.columnHeight=25]
* @param {Number} [_config.header.columnPadding=3]
* @param {Number} [_config.header.columnBorderWidth=1]
* @param {Object} [_config.body]
* @param {Function} [_config.body.onClick]
* @param {Function} [_config.body.onDBLClick]
* @param {Function} [_config.body.onDataChanged]
* @param {String|Array} [_config.body.mergeCells=false] -
* @param {String} [_config.body.align]
* @param {Number} [_config.body.columnHeight=25]
* @param {Number} [_config.body.columnPadding=3]
* @param {Number} [_config.body.columnBorderWidth=1]
* @param {Object} [_config.body.grouping]
* @param {Array} [_config.body.grouping.by] - list grouping keys
* @param {Array} [_config.body.grouping.columns] - list grouping columns
* @param {(String|Function)} [_config.body.trStyleClass]
*
* @param {Object} [_config.page]
* @param {Number} [_config.page.height=25]
* @param {Boolean} [_config.page.display=true] - grid page display
* @param {Boolean} [_config.page.statusDisplay=true] - grid status display
* @param {Number} [_config.page.navigationItemCount=5]
* @param {Object} [_config.scroller]
* @param {Number} [_config.scroller.size=15]
* @param {Number} [_config.scroller.barMinSize=15]
* @param {Number} [_config.scroller.trackPadding=4]
* @param {Object} [_config.columnKeys]
* @param {String} [_config.columnKeys.selected="_SELECTED"]
* @param {Object[]} _config.columns
* @param {String} _config.columns[].key
* @param {String} _config.columns[].label
* @param {Number} _config.columns[].width
* @param {(String|Function)} _config.columns[].styleClass
* @param {(String|Function)} _config.columns[].headerStyleClass
* @param {Boolean} _config.columns[].enableFilter
* @param {Boolean} _config.columns[].sortable
* @param {String} _config.columns[].align
* @param {(String|Function)} _config.columns[].formatter
* @param {Object} _config.columns[].editor
* @param {String} _config.columns[].editor.type - text,number,money,date
* @param {Object} _config.columns[].editor.config
* @param {Array} _config.columns[].editor.updateWith
* @param {Function} _config.columns[].editor.disabled - disable editor
* @param {Boolean} [_config.columns[].multiLine=false]
* @param {Object} [_config.tree]
* @param {Boolean} [_config.tree.use=false] - Whether tree-type data is used
* @param {Number} [_config.tree.hashDigit=8]
* @param {Number} [_config.tree.indentWidth=10]
* @param {Number} [_config.tree.arrowWidth=15]
* @param {Number} [_config.tree.iconWidth=18]
* @param {Object} [_config.tree.icons]
* @param {String} [_config.tree.icons.openedArrow='▾']
* @param {String} [_config.tree.icons.collapsedArrow='▸']
* @param {String} [_config.tree.icons.groupIcon='⊚']
* @param {String} [_config.tree.icons.collapsedGroupIcon='⊚']
* @param {String} [_config.tree.icons.itemIcon='⊙']
* @param {Object} [_config.tree.columnKeys]
* @param {String} [_config.tree.columnKeys.parentKey="pid"]
* @param {String} [_config.tree.columnKeys.selfKey="id"]
* @param {String} [_config.tree.columnKeys.collapse="collapse"]
* @param {String} [_config.tree.columnKeys.hidden="hidden"]
* @param {String} [_config.tree.columnKeys.parentHash="__hp__"]
* @param {String} [_config.tree.columnKeys.selfHash="__hs__"]
* @param {String} [_config.tree.columnKeys.children="__children__"]
* @param {String} [_config.tree.columnKeys.depth="__depth__"]
* @returns {ax5grid}
* @example
* ```js
* var firstGrid = new ax5.ui.grid();
*
* ax5.ui.grid.formatter["myType"] = function () {
* return "myType" + (this.value || "");
* };
* ax5.ui.grid.formatter["capital"] = function(){
* return (''+this.value).toUpperCase();
* };
*
* ax5.ui.grid.collector["myType"] = function () {
* return "myType" + (this.value || "");
* };
*
* var sampleData = [
* {a: "A", b: "A01", price: 1000, amount: 12, cost: 12000, saleDt: "2016-08-29", customer: "장기영", saleType: "A"},
* {companyJson: {"대표자명":"abcd"}, a: "A", b: "B01", price: 1100, amount: 11, cost: 12100, saleDt: "2016-08-28", customer: "장서우", saleType: "B"},
* {companyJson: {"대표자명":"abcd"}, a: "A", b: "C01", price: 1200, amount: 10, cost: 12000, saleDt: "2016-08-27", customer: "이영희", saleType: "A"},
* {companyJson: {"대표자명":"위세라"}, a: "A", b: "A01", price: 1300, amount: 8, cost: 10400, saleDt: "2016-08-25", customer: "황인서", saleType: "C"},
* {companyJson: {"대표자명":"abcd"}, a: "A", b: "B01", price: 1400, amount: 5, cost: 7000, saleDt: "2016-08-29", customer: "황세진", saleType: "D"},
* {companyJson: {"대표자명":"abcd"}, a: "A", b: "A01", price: 1500, amount: 2, cost: 3000, saleDt: "2016-08-26", customer: "이서연", saleType: "A"}
* ];
*
* var gridView = {
* initView: function () {
* firstGrid.setConfig({
* target: $('[data-ax5grid="first-grid"]'),
* columns: [
* {
* key: "companyJson['대표자명']",
* label: "필드A",
* width: 80,
* styleClass: function () {
* return "ABC";
* },
* enableFilter: true,
* align: "center",
* editor: {type:"text"}
* },
* {key: "b", label: "필드B", align: "center"},
* {
* key: undefined, label: "필드C", columns: [
* {key: "price", label: "단가", formatter: "money", align: "right"},
* {key: "amount", label: "수량", formatter: "money", align: "right"},
* {key: "cost", label: "금액", align: "right", formatter: "money"}
* ]
* },
* {key: "saleDt", label: "판매일자", align: "center"},
* {key: "customer", label: "고객명"},
* {key: "saleType", label: "판매타입"}
* ]
* });
* return this;
* },
* setData: function (_pageNo) {
* firstGrid.setData(sampleData);
* return this;
* }
* };
*
* // onClick, onDBLClick, onDataChanged
* firstGrid.setConfig({
* target: $('[data-ax5grid="first-grid"]'),
* columns: [...],
* body: {
* onClick: function(){
* console.log(this);
* },
* onDBLClick: function(){
* console.log(this);
* // If the column does not have an editor attribute, an event is raised.
* },
* onDataChanged: function(){
* console.log(this);
* // If change Data
* }
* }
* });
* ```
*/
this.init = function (_config) {
cfg = jQuery.extend(true, {}, cfg, _config);
if (!cfg.target) {
console.log(ax5.info.getError("ax5grid", "401", "init"));
return this;
}
// 그리드의 이벤트 정의 구간
this.onStateChanged = cfg.onStateChanged;
this.onClick = cfg.onClick;
//this.onDblClick = cfg.onDblClick;
this.onLoad = cfg.onLoad;
this.onDataChanged = cfg.body.onDataChanged;
// todo event에 대한 추가 정의 필요
this.$target = jQuery(cfg.target);
// target attribute data
(function (data) {
if (U.isObject(data) && !data.error) {
cfg = jQuery.extend(true, cfg, data);
}
}).call(this, U.parseJson(this.$target.attr("data-ax5grid-config"), true));
var grid = this.config = cfg;
if (!this.config.height) {
this.config._height = this.$target.height();
}
if (!this.id) this.id = this.$target.data("data-ax5grid-id");
if (!this.id) {
//this.id = 'ax5grid-' + ax5.getGuid();
this.id = 'ax5grid-' + this.instanceId;
this.$target.data("data-ax5grid-id", grid.id);
}
GRID.data.init.call(this);
if (this.config.tree.use) {
// 트리라면
this.sortInfo = {};
this.sortInfo[this.config.tree.columnKeys.selfHash] = { orderBy: "asc", seq: 0, fixed: true };
}
///========
// 그리드를 그리기 위한 가장 기초적인 작업 뼈대와 틀을 준비합니다. 이 메소드는 초기화 시 한번만 호출 되게 됩니다.
initGrid.call(this);
// columns데이터를 분석하여 미리 처리해야하는 데이터를 정리합니다.
initColumns.call(this, grid.columns);
resetColGroupWidth.call(this);
// footSum 데이터를 분석하여 미리 처리해야 하는 데이터를 정리
if (grid.footSum) initFootSum.call(this, grid.footSum);
// bodyGrouping 데이터를 분석하여 미리 처리해야 하는 데이터를 정리
if (grid.body.grouping) initBodyGroup.call(this, grid.body.grouping);
// 그리드의 각 요소의 크기를 맞춤니다.
alignGrid.call(this, true);
// columns의 데이터로 header데이터를 만들고
GRID.header.init.call(this);
// header를 출력합니다.
GRID.header.repaint.call(this);
// columns의 데이터로 body데이터를 만들고
GRID.body.init.call(this);
// body를 출력합니다.
GRID.body.repaint.call(this);
// scroller
GRID.scroller.init.call(this);
GRID.scroller.resize.call(this);
jQuery(window).bind("resize.ax5grid-" + this.id, function () {
alignGrid.call(self);
GRID.scroller.resize.call(self);
GRID.body.repaint.call(self); // window resize시 repaint 함수 호출
});
jQuery(document.body).on("click.ax5grid-" + this.id, function (e) {
var isPickerClick = false,
target = U.findParentNode(e.target, function (_target) {
if (isPickerClick = _target.getAttribute("data-ax5grid-inline-edit-picker")) {
return true;
}
return _target.getAttribute("data-ax5grid-container") === "root";
});
if (target && target.getAttribute("data-ax5grid-instance") === this.id) {
self.focused = true;
} else {
self.focused = false;
GRID.body.blur.call(this);
}
}.bind(this));
jQuery(window).on("keydown.ax5grid-" + this.instanceId, function (e) {
if (self.focused) {
if (self.isInlineEditing) {
if (e.which == ax5.info.eventKeys.ESC) {
self.keyDown("ESC", e.originalEvent);
} else if (e.which == ax5.info.eventKeys.RETURN) {
self.keyDown("RETURN", e.originalEvent);
} else if (e.which == ax5.info.eventKeys.TAB) {
self.keyDown("TAB", e.originalEvent);
U.stopEvent(e);
} else if (e.which == ax5.info.eventKeys.UP) {
self.keyDown("RETURN", { shiftKey: true });
} else if (e.which == ax5.info.eventKeys.DOWN) {
self.keyDown("RETURN", {});
}
} else {
if (e.metaKey || e.ctrlKey) {
if (e.which == 67) {
// c
self.copySelect();
}
} else {
if (ctrlKeys[e.which]) {
self.keyDown(ctrlKeys[e.which], e.originalEvent); // 키다운 이벤트 호출
U.stopEvent(e);
} else if (e.which == ax5.info.eventKeys.ESC) {
if (self.focused) {
GRID.body.blur.call(self);
}
} else if (e.which == ax5.info.eventKeys.RETURN || e.which == ax5.info.eventKeys.SPACE) {
self.keyDown("RETURN", e.originalEvent);
} else if (e.which == ax5.info.eventKeys.TAB) {
//self.keyDown("RETURN", e.originalEvent);
U.stopEvent(e);
} else if (Object.keys(self.focusedColumn).length) {
/*
self.keyDown("INLINE_EDIT", e.originalEvent);
*/
}
}
}
}
});
jQuery(window).on("keyup.ax5grid-" + this.instanceId, function (e) {
if (self.focused) {
if (self.isInlineEditing) {} else {
if (e.metaKey || e.ctrlKey) {} else {
if (ctrlKeys[e.which]) {} else if (e.which == ax5.info.eventKeys.ESC) {} else if (e.which == ax5.info.eventKeys.RETURN || e.which == ax5.info.eventKeys.SPACE) {} else if (e.which == ax5.info.eventKeys.TAB) {} else if (Object.keys(self.focusedColumn).length) {
self.keyDown("INLINE_EDIT", e.originalEvent);
}
}
}
}
});
// 그리드 레이아웃이 모든 준비를 마친시점에 onLoad존재 여부를 확인하고 호출하여 줍니다.
setTimeout(function () {
if (this.onLoad) {
this.onLoad.call({
self: this
});
}
}.bind(this));
return this;
};
/**
* align grid size
* @method ax5grid.align
* @returns {ax5grid}
* @example
* ```js
* ax5Grid.repaint();
* ```
*/
this.align = function () {
if (alignGrid.call(this)) {
GRID.body.repaint.call(this);
GRID.scroller.resize.call(this);
}
return this;
};
/**
* repaint grid
* @method ax5grid.repaint
* @return {ax5grid}
* @example
* ```js
* ax5Grid.repaint();
* ```
*/
this.repaint = function () {
GRID.header.repaint.call(this);
GRID.body.repaint.call(this, true); // 강제로 다시 그리기
GRID.scroller.resize.call(this);
return this;
};
/**
* @method ax5grid.keyDown
* @param {String} _keyName
* @param {Event|Object} _data
* @return {ax5grid}
*/
this.keyDown = function () {
var processor = {
"KEY_UP": function KEY_UP() {
GRID.body.moveFocus.call(this, "UP");
},
"KEY_DOWN": function KEY_DOWN() {
GRID.body.moveFocus.call(this, "DOWN");
},
"KEY_LEFT": function KEY_LEFT() {
GRID.body.moveFocus.call(this, "LEFT");
},
"KEY_RIGHT": function KEY_RIGHT() {
GRID.body.moveFocus.call(this, "RIGHT");
},
"KEY_HOME": function KEY_HOME() {
GRID.body.moveFocus.call(this, "HOME");
},
"KEY_END": function KEY_END() {
GRID.body.moveFocus.call(this, "END");
},
"INLINE_EDIT": function INLINE_EDIT(_e) {
GRID.body.inlineEdit.active.call(this, this.focusedColumn, _e);
if (!/[0-9a-zA-Z]/.test(_e.key)) {
U.stopEvent(_e);
}
},
"ESC": function ESC(_e) {
GRID.body.inlineEdit.keydown.call(this, "ESC");
},
"RETURN": function RETURN(_e) {
var activeEditLength = 0;
for (var columnKey in this.inlineEditing) {
activeEditLength++;
if (!GRID.body.inlineEdit.keydown.call(this, "RETURN", columnKey)) {
return false;