jui.defineUI("grid.xtable", [ "jquery", "util.base", "ui.modal", "grid.table", "grid.row" ], function($, _, modal, table, Row) {
_.resize(function() {
var call_list = jui.get("grid.xtable");
for(var i = 0; i < call_list.length; i++) {
var ui_list = call_list[i];
for(var j = 0; j < ui_list.length; j++) {
ui_list[j].resize();
}
}
}, 1000);
/**
* @class grid.xtable
* @extends core
* @alias X-Table
* @requires util.base
* @requires ui.modal
* @requires grid.table
*
*/
var UI = function() {
var head = null, body = null;
var rows = [], c_rows = [], t_rows = [], o_rows = null; // 루트 rows, 루트 rows 인덱스, 자식 포함 rows, 자식 제외 + 필터 rows (리펙토링 필요함!!!)
var ui_modal = null, page = 1;
var is_loading = false, is_resize = false;
var w_resize = 8, select_row = null;
var iParser = _.index();
var vscroll_info = null;
var xss_filter_keys = null;
function createRows(data, no, pRow, type) {
var tmp_rows = [];
for(var i = 0, len = data.length; i < len; i++) {
var row = new Row(),
rownum = no + i;
// row 객체 초기화
row.init(data[i], head.tpl["row"], pRow);
row.setIndex(rownum);
// row 상태 설정
if(type == "open" || type == "fold") {
row.type = type;
}
// 루트 row만 캐싱함
if(pRow == null) {
c_rows[rownum] = row;
}
tmp_rows.push(row);
}
return tmp_rows;
}
function createTableList(self) {
var exceptOpts = [
"buffer", "bufferCount", "csvCount", "sortLoading", "sortCache", "sortIndex", "sortOrder",
"event", "rows", "scrollWidth", "width", "rowHeight", "xssFilter", "msort"
];
var $root = $(self.root);
// 가상스크롤 모드일 때, 로우 높이는 고정되야 하므로 nowrap 클래스를 추가함
if(self.options.buffer == "vscroll") {
if(!$root.hasClass("nowrap")) {
$root.addClass("nowrap");
}
}
// 스크롤 모드일 때, 무조건 scroll 클래스를 추가함
if(self.options.buffer != "page") {
if(!$root.hasClass("scroll")) {
$root.addClass("scroll");
}
}
// 기본 테이블 마크업 복사해서 추가하기
$root.append($root.children("table").clone());
head = table($root.children("table:first-child"), getExceptOptions(self, exceptOpts)); // 헤더 테이블 생성
setTableHeadStyle(self, head);
body = table($root.children("table:last-child"), getExceptOptions(self, exceptOpts.concat("resize"))); // 바디 테이블 생성
setTableBodyStyle(self, body); // X-Table 생성 및 마크업 설정
// 공통 테이블 스타일 정의
setTableAllStyle(self, head, body);
// TODO: XSS 필터 대상 컬럼 설정 리펙토링 필요
if(self.options.xssFilter) {
var filterIndexes = self.options.xssFilter,
len = (filterIndexes === true) ? head.uit.getColumnCount() : filterIndexes.length;
xss_filter_keys = {};
for(var i = 0; i < len; i++) {
var colKey = (filterIndexes === true) ? i : filterIndexes[i],
col = head.getColumn(colKey);
xss_filter_keys[col.name] = true;
}
}
// 테이블 옵션 필터링 함수
function getExceptOptions(self, exceptOpts) {
var options = {};
for(var key in self.options) {
if($.inArray(key, exceptOpts) == -1) {
options[key] = self.options[key];
}
}
// 가로 스크롤 모드일 때, resize 옵션 막기
if(self.options.scrollWidth > 0) {
options.resize = false;
}
return options;
}
function setTableAllStyle(self, head, body) {
var opts = self.options;
if(opts.scrollWidth > 0) {
self.scrollWidth(opts.scrollWidth, true);
} else {
if(opts.width > 0) {
$(self.root).outerWidth(opts.width);
}
}
}
function setTableHeadStyle(self, head) {
$(head.root).wrap("<div class='head'></div>");
$(head.root).children("tbody").remove();
}
function setTableBodyStyle(self, body) {
var cols = body.listColumn();
// X-Table 바디 영역 스크롤 높이 설정
if (self.options.buffer != "page") {
var scrollHeight = self.options.scrollHeight;
if (self.options.buffer == "vscroll") {
$(body.root).wrap("<div class='body' style='max-height: " + scrollHeight + "px'><div></div></div>");
$(body.root).parent().parent().css({
"overflow-y": "scroll"
});
} else {
$(body.root).wrap("<div class='body' style='max-height: " + scrollHeight + "px'></div>");
$(body.root).parent().css({
"overflow-y": "scroll"
});
}
} else {
$(body.root).wrap("<div class='body'></div>");
}
// X-Table 바디 영역의 헤더라인은 마지막 노드를 제외하고 제거
$(body.root).find("thead > tr").outerHeight(0).not(":last-child").remove();
// X-Table 바디 영역의 헤더 설정
for(var i = 0; i < cols.length; i++) {
$(cols[i].element).html("").outerHeight(0);
}
}
}
function setCustomEvent(self) {
head.on("colresize", function(column, e) { // 컬럼 리사이징 관련
var cols = head.listColumn(),
bodyCols = body.listColumn(),
isLast = false;
for(var j = cols.length - 1; j >= 0; j--) {
var hw = $(cols[j].element).outerWidth();
if(self.options.buffer != "page" && cols[j].type == "show" && !isLast) {
if(_.browser.msie) {
$(bodyCols[j].element).outerWidth(hw - getScrollBarWidth(self));
} else {
$(bodyCols[j].element).css({ "width": "auto" });
}
isLast = true;
} else {
$(cols[j].element).outerWidth(hw);
$(bodyCols[j].element).outerWidth(hw);
}
}
reloadScrollWidthResizeBar(500);
self.emit("colresize", [ column, e ]);
});
head.on("colshow", function(column, e) {
body.uit.showColumn(column.index);
self.resize();
self.emit("colshow", [ column, e ]);
});
head.on("colhide", function(column, e) {
body.uit.hideColumn(column.index);
self.resize();
self.emit("colhide", [ column, e ]);
});
head.on("colclick", function(column, e) {
self.emit("colclick", [ column, e ]);
});
head.on("coldblclick", function(column, e) {
self.emit("coldblclick", [ column, e ]);
});
head.on("colmenu", function(column, e) {
self.emit("colmenu", [ column, e ]);
});
head.on("sort", function(column, e) {
self.sort(column.index, column.order, false, e);
self.emit("sort", [ column, e ]);
});
body.on("select", function(obj, e) {
self.emit("select", [ obj, e ]);
});
body.on("click", function(obj, e) {
self.emit("click", [ obj, e ]);
});
body.on("dblclick", function(obj, e) {
self.emit("dblclick", [ obj, e ]);
});
body.on("rowmenu", function(obj, e) {
self.emit("rowmenu", [ obj, e ]);
});
body.on("expand", function(obj, e) {
self.emit("expand", [ obj, e ]);
});
body.on("expandend", function(obj, e) {
self.emit("expandend", [ obj, e ]);
});
}
function setScrollEvent(self, width, height) {
var opts = self.options;
var $head = $(self.root).children(".head"),
$body = $(self.root).children(".body");
self.addEvent($body, "scroll", function(e) {
// 컬럼 메뉴는 스크롤시 무조건 숨기기
self.hideColumnMenu();
// 가로 스크롤
if(width > 0) {
$head.scrollLeft(this.scrollLeft);
}
if(opts.buffer == "scroll") { // 무조건 scroll 타입일 때
var scrollTop = this.scrollTop + height,
scrollHeight = $body.get(0).scrollHeight;
if (scrollTop >= scrollHeight * 0.9) {
self.next();
self.emit("scroll", e);
}
} else if(opts.buffer == "vscroll") {
if(vscroll_info.prev_scroll_left == this.scrollLeft) {
renderVirtualScroll(self);
self.next();
self.emit("scroll", e);
} else {
vscroll_info.prev_scroll_left = this.scrollLeft;
}
}
return false;
});
// 스크롤 키보드 이벤트 설정
if(opts.buffer == "vscroll") {
$(self.root).hover(function() {
vscroll_info.is_focus = true;
}, function() {
vscroll_info.is_focus = false;
});
self.addEvent(document, "keydown", function (e) {
if(vscroll_info.is_focus) {
var top = $body.scrollTop(),
tick = self.options.rowHeight;
if (e.which == 38 || e.which == 40) {
$body.scrollTop(top + ((e.which == 38) ? -tick : tick));
} else if (e.which == 33 || e.which == 34) {
var newTick = tick * vscroll_info.scroll_count;
$body.scrollTop(top + ((e.which == 33) ? -newTick : newTick));
}
}
});
}
}
function setScrollWidthResize(self) {
var column = {},
width = {},
resizeX = 0;
// 리사이즈 엘리먼트 삭제
$(self.root).find("thead .resize").remove();
for(var i = 0, len = head.uit.getColumnCount(); i < len; i++) {
var $colElem = $(head.getColumn(i).element),
$resizeBar = $("<div class='resize'></div>");
var pos = $colElem.position(),
left = $colElem.outerWidth() + pos.left - 1;
$resizeBar.css({
position: "absolute",
width: w_resize + "px",
height: $colElem.outerHeight(),
left: ((i == len - 1) ? left - w_resize : left) + "px",
top: pos.top + "px",
cursor: "w-resize",
"z-index": "1"
});
$colElem.append($resizeBar);
// Event Start
(function(index, isLast) {
self.addEvent($resizeBar, "mousedown", function(e) {
if(resizeX == 0) {
resizeX = e.pageX;
}
// 컬럼 객체 가져오기
column = {
head: head.getColumn(index),
body: body.getColumn(index),
isLast: isLast
};
width = {
column: $(column.head.element).outerWidth(),
head: $(head.root).outerWidth(),
body: $(body.root).outerWidth(),
"max-width": parseInt($(head.root).parent().css("max-width"))
};
is_resize = true;
return false;
});
})(i, i == len - 1);
}
self.addEvent(document, "mousemove", function(e) {
if(resizeX > 0) {
colResizeWidth(e.pageX - resizeX);
}
});
self.addEvent(document, "mouseup", function(e) {
if(resizeX > 0) {
// 마지막 컬럼 크기를 0보다 크게 리사이징시 가로 스크롤 위치 조정
if(column.isLast) {
var scrollLeft = $(body.root).parent().scrollLeft(),
disWidth = e.pageX - resizeX;
if(disWidth > 0) {
$(head.root).parent().scrollLeft(scrollLeft + disWidth);
$(body.root).parent().scrollLeft(scrollLeft + disWidth);
}
}
// 스크롤 위치 초기화
resizeX = 0;
// 리사이징 바, 위치 이동
reloadScrollWidthResizeBar(500);
head.emit("colresize", [ column.head, e ]);
// 리사이징 상태 변경 (delay)
setTimeout(function() {
is_resize = false;
}, 100);
return false;
}
});
// 리사이징 바 위치 설정
head.on("colshow", reloadScrollWidthResizeBar);
head.on("colhide", reloadScrollWidthResizeBar);
function colResizeWidth(disWidth) {
var colMinWidth = 30;
// 전체 최소 크기 체크
if (width.head + disWidth < width["max-width"]) {
return;
}
// 컬럼 최소 크기 체크
if (width.column + disWidth < colMinWidth)
return;
$(column.head.element).outerWidth(width.column + disWidth);
$(column.body.element).outerWidth(width.column + disWidth);
$(head.root).outerWidth(width.head + disWidth);
$(body.root).outerWidth(width.body + disWidth);
}
}
function reloadScrollWidthResizeBar(delay) {
setTimeout(function() {
for(var i = 0, len = head.uit.getColumnCount(); i < len; i++) {
var $colElem = $(head.getColumn(i).element);
var pos = $colElem.position(),
left = $colElem.outerWidth() + pos.left - 1;
$colElem.find(".resize").css("left", ((i == len - 1) ? left - w_resize : left) + "px");
}
}, delay);
}
function getScrollBarWidth(self) {
return self.options.buffer == "page" ? 0 : _.scrollWidth() + 1;
}
function renderVirtualScroll(self) {
var $viewport = $(self.root).children(".body");
var viewportHeight = self.options.scrollHeight,
scrollTop = $viewport.scrollTop(),
scrollHeight = $viewport[0].scrollHeight;
// calculate
var dist = Math.abs(vscroll_info.prev_scroll_top - scrollTop);
var isDown = vscroll_info.prev_scroll_top < scrollTop;
var v_height = vscroll_info.height;
if (dist == 0) {
return;
}
var isBlock = false;
if (dist < viewportHeight ) {
isBlock = true;
// move short dist
if (dist !== 0) {
if (dist < v_height) {
var distIndex = Math.ceil(dist / v_height);
} else {
var distIndex = Math.floor(dist / v_height);
}
if (isDown == false) {
vscroll_info.current_row_index -= distIndex;
} else {
vscroll_info.current_row_index += distIndex;
}
}
} else {
// move long dist
var rate = scrollTop / (scrollHeight - viewportHeight),
limit = Math.floor(vscroll_info.count - vscroll_info.scroll_count);
vscroll_info.current_row_index = Math.ceil(limit * rate);
}
// traverse real content
var startIndex = vscroll_info.current_row_index,
endIndex = startIndex,
endRowHeight = v_height;
while(endRowHeight < viewportHeight && endIndex < vscroll_info.count) {
endIndex++;
endRowHeight += v_height;
}
// 전체 표시 길이가 높이 보다 작을 때
if (endRowHeight < viewportHeight) {
// 전체 content 자체가 작다면
if (viewportHeight > vscroll_info.content_height) {
} else {
// 목록이 긴데 마지막이 짧다면
var hiddenRowCount = Math.ceil(Math.abs(viewportHeight - endRowHeight) / v_height);
startIndex -= hiddenRowCount;
endRowHeight += hiddenRowCount * v_height;
}
}
if (startIndex < 0) {
vscroll_info.prev_scroll_top = 0;
return;
}
// 시작지점으로 스크롤탑 다시 보정.
if (isBlock) {
if (endIndex !== vscroll_info.count -1) {
scrollTop = Math.ceil(startIndex / (vscroll_info.count) * scrollHeight);
$viewport.scrollTop(scrollTop);
scrollTop = $viewport.scrollTop();
}
}
var moveHeight = 0;
var real_viewportHeight = (viewportHeight + vscroll_info.height);
if (scrollTop >= scrollHeight - real_viewportHeight) {
if (endRowHeight > real_viewportHeight) {
moveHeight = -Math.abs(endRowHeight - real_viewportHeight);
}
}
// save prev scroll top
vscroll_info.prev_scroll_top = scrollTop;
// set real content height
$viewport.css({ "max-height": endRowHeight });
$(body.root).css({ top: (vscroll_info.prev_scroll_top + moveHeight) + "px" });
vscroll_info.start_index = startIndex;
vscroll_info.end_index = endIndex + 1;
}
function setVirtualScrollInfo(self) {
vscroll_info.height = self.options.rowHeight;
vscroll_info.count = t_rows.length;
vscroll_info.scroll_count = Math.floor(self.options.scrollHeight / vscroll_info.height);
vscroll_info.content_height = vscroll_info.count * vscroll_info.height;
$(body.root).parent().height(vscroll_info.content_height > 0 ? vscroll_info.content_height : "auto");
}
function resetVirtualScrollInfo(self) {
$(self.root).find(".body").scrollTop(0);
$(body.root).css({ top: "0px" });
$(body.root).parent().css({ height: "auto" });
vscroll_info = {
height: 0,
content_height: 0,
count: 0,
scroll_count: 0,
prev_scroll_left: 0,
prev_scroll_top: 0,
current_row_index: 0,
start_index: 0,
end_index: 0,
is_focus: true
};
}
function setOpenChildRows(rows) {
for(var i = 0; i < rows.length; i++) {
t_rows.push(rows[i]);
if(rows[i].type == "open" && rows[i].children.length > 0) {
setOpenChildRows(rows[i].children);
}
}
}
function appendChildRows(p_row, data, type) {
var no = p_row.children.length,
c_rows = createRows(_.typeCheck("array", data) ? data : [ data ], no, p_row, type);
for(var i = 0, len = c_rows.length; i < len; i++) {
p_row.children.push(c_rows[i]);
}
}
function calculateRows(self, isTree) {
if(isTree) {
t_rows = [];
setOpenChildRows(rows);
} else {
t_rows = rows;
}
if(self.options.buffer == "vscroll") { // 가상 스크롤 설정
setVirtualScrollInfo(self);
}
}
function setEventMultiSort(self) {
var sortIndexes = self.options.msort,
len = (sortIndexes === true) ? head.uit.getColumnCount() : sortIndexes.length,
msort_columns = [],
msort_orders = [];
for(var i = 0; i < len; i++) {
var colKey = (sortIndexes === true) ? i : sortIndexes[i],
col = self.getColumn(colKey);
if(col.element != null) {
(function(index, column) {
self.addEvent(column.element, "click", function(e) {
if($(e.target).hasClass("resize")) return;
if(column.order == "asc") {
column.order = null;
for(var j = 0; j < msort_columns.length; j++) {
if(column.name == msort_columns[j]) {
msort_columns.splice(j, 1);
msort_orders.splice(j, 1);
}
}
} else {
var colIndex = _.inArray(column.name, msort_columns);
if(column.order == null) {
column.order = "desc";
} else if(column.order == "desc") {
column.order = "asc";
}
if(colIndex == -1) {
msort_columns.push(column.name);
msort_orders.push(column.order);
} else {
msort_orders[colIndex] = column.order;
}
}
self.emit("msort", [ column, e ]);
self.msort(msort_columns, msort_orders);
self.emit("colclick", [ column, e ]);
});
})(colKey, col);
$(col.element).css("cursor", "pointer");
}
}
}
function recursiveMultiSort(a, b, columns, order_by, index) {
var direction = (order_by[index] == "desc") ? 1 : 0,
key = columns[index],
is_numeric = !isNaN(+a[key] - +b[key]),
x = is_numeric ? +a[key] : a[key],
y = is_numeric ? +b[key] : b[key];
if(!is_numeric) {
if(typeof(x) == "string") x = x.toLowerCase();
if(typeof(y) == "string") y = y.toLowerCase();
}
if(x < y) {
return direction == 0 ? -1 : 1;
}
if(x == y) {
return columns.length-1 > index ? recursiveMultiSort(a,b,columns,order_by,index+1) : 0;
}
return direction == 0 ? 1 : -1;
}
function printLogForVsInfo(info) {
if(
info.start_index < 0 || isNaN(info.start_index) ||
info.end_index < 0 || isNaN(info.end_index) ||
info.current_row_index < 0 || isNaN(info.current_row_index) ||
info.scroll_count < 0 || isNaN(info.scroll_count) ||
info.prev_scroll_top < 0 || isNaN(info.prev_scroll_top)
) {
console.log(info);
}
}
this.init = function() {
var opts = this.options;
// @Deprecated, 'rows'는 의미상 맞지 않아 차후 삭제
opts.data = (opts.rows != null) ? opts.rows : opts.data;
// 루트가 테이블일 경우, 별도 처리
if(this.root.tagName == "TABLE") {
var $root = $(this.root).wrap("<div class='xtable'></div>");
this.root = $root.parent().get(0);
}
// 기본 설정
createTableList(this);
setCustomEvent(this);
// 가로/세로 스크롤 설정
setScrollEvent(this, opts.scrollWidth, opts.scrollHeight);
// 멀티소트 이벤트 설정
if(opts.msort && opts.sortEvent) {
setEventMultiSort(this);
}
// 데이터가 있을 경우
if(opts.data) {
this.update(opts.data);
}
// 로딩 템플릿 체크 (opts.sortLoading으로 체크하지 않음)
if(head.tpl["loading"] && modal != null) {
var $loading = $(head.tpl["loading"]());
$(this.root).append($loading);
ui_modal = modal($loading, {
target: this.selector,
opacity: 0.1,
autoHide: false
});
// 기본 로딩 시간 (ms)
opts.sortLoading = (opts.sortLoading === true) ? 500 : opts.sortLoading;
}
// 컬럼 리사이징 (기본)
if(opts.resize) {
if(opts.scrollWidth > 0) {
setScrollWidthResize(this);
} else {
head.resizeColumns();
head.resize();
}
}
}
this.render = function(isTree) {
calculateRows(this, isTree);
this.next();
}
/**
* @method select
* Adds a selected class to a row at a specified index and gets an instance of the applicable row.
*
* @param {Integer} index
* @return {RowObject} row
*/
this.select = function(index) {
if(select_row != null) {
$(select_row.element).removeClass("selected");
}
var row = this.get(index);
select_row = row;
if(row.element != null) {
$(row.element).addClass("selected");
}
return row;
}
/**
* @method unselect
* Removes a selected class from a selected row and gets an instance of the row in question.
*
* @return {RowObject} row
*/
this.unselect = function() {
if(select_row != null) {
$(select_row.element).removeClass("selected");
select_row = null;
}
}
/**
* @method update
* Updates the list of rows or modifies the row at a specified index.
*
* @param {Array} rows
*/
this.update = function(dataList) {
this.reset();
rows = createRows(dataList, 0, null);
this.render();
this.emit("update");
head.emit("colresize");
// 정렬 인덱스가 옵션에 있을 경우, 해당 인덱스의 컬럼 정렬 (not loading)
if(this.options.sortIndex) {
if(this.options.sort) {
this.sort(this.options.sortIndex, this.options.sortOrder, true);
} else if(this.options.msort) {
this.msort(this.options.sortIndex, this.options.sortOrder, true);
}
}
}
/**
* @method updateTree
* It is possible to configure a tree table using an object array with the index and data properties.
*
* @param {Array} rows
*/
this.updateTree = function(tree) {
this.reset();
for(var i = 0; i < tree.length; i++) {
var pIndex = iParser.getParentIndex(tree[i].index);
if(pIndex == null) {
rows.push(createRows([ tree[i].data ], 0, null, tree[i].type)[0]);
} else {
var pRow = this.get(pIndex);
if(pRow) {
appendChildRows(pRow, tree[i].data, tree[i].type);
}
}
}
this.render(true);
this.emit("updateTree");
}
/**
* @method append
* Add a row or a child row to at a specified index.
*
* @param {RowObject} row
* @param {RowObject} row
*/
this.append = function(index, data) {
var row = this.get(index);
if(row) {
appendChildRows(row, data);
this.clear();
this.render(true);
this.emit("append");
}
}
/**
* @method open
* Shows a child row of a specified index.
*
* @param {Integer} index
*/
this.open = function(index) { // 로트 제외, 하위 모든 노드 대상
var row = this.get(index);
if(row) {
row.type = "open";
this.clear();
this.render(true);
this.emit("open", [row]);
}
}
/**
* @method fold
* Hides a child row of a specified index.
*
* @param {Integer} index
*/
this.fold = function(index) {
var row = this.get(index);
if(row) {
row.type = "fold";
this.clear();
this.render(true);
this.emit("fold", [row]);
}
}
/**
* @method openAll
* Shows all child rows of a specified index.
*/
this.openAll = function(index) {
var list = this.getAll(index);
if(list) {
for(var i = 0, len = list.length; i < len; i++) {
list[i].type = "open";
}
this.clear();
this.render(true);
this.emit("openall");
}
}
/**
* @method foldAll
* Hides all child rows of a specified index.
*/
this.foldAll = function(index) {
var list = this.getAll(index);
if(list) {
for(var i = 0, len = list.length; i < len; i++) {
list[i].type = "fold";
}
this.clear();
this.render(true);
this.emit("foldall");
}
}
/**
* @method next
* Changes to the next page.
*/
this.next = function() {
var start = (page - 1) * this.options.bufferCount,
end = start + this.options.bufferCount;
// 가상스크롤일 때만 처리
if(this.options.buffer == "vscroll") {
body.reset();
if(vscroll_info.start_index < vscroll_info.end_index) {
start = vscroll_info.start_index;
end = vscroll_info.end_index;
}
printLogForVsInfo(vscroll_info);
}
// 마지막 페이지 처리
end = (end > t_rows.length) ? t_rows.length : end;
if(end <= t_rows.length) {
var tmpDataList = [];
for(var i = start; i < end; i++) {
var r = t_rows[i];
r.seq = i + 1;
r.reload(head.uit.getColumn(), null, xss_filter_keys);
tmpDataList.push(r);
}
body.append(tmpDataList);
// 가상스크롤이 아닐 경우에만 추가
if(this.options.buffer != "vscroll") {
this.emit("next", [ page ]);
if (tmpDataList.length > 0) page++;
}
}
}
/**
* @method page
* Changes to the page of at a specified index.
*
* @param {Integer} index
*/
this.page = function(pNo) {
if(this.options.buffer == "scroll" || this.options.buffer == "vscroll")
return false;
if(this.getPage() == pNo) return false;
this.clear();
page = (pNo < 1) ? 1 : pNo;
this.render();
}
/**
* @method sort
* Moves a row iat a specified index to the target index.
*
* @param {Integer} index
* @param {String} order "asc" or "desc"
*/
this.sort = function(index, order, isNotLoading, e) { // index는 컬럼 key 또는 컬럼 name
if(!this.options.fields || !this.options.sort || this.options.msort || is_resize) return;
var self = this,
column = head.getColumn(index);
if(typeof(column.name) == "string") {
column.order = (order) ? order : (column.order == "asc" || column.order == null) ? "desc" : "asc";
head.uit.setColumn(index, column);
if(this.options.sortLoading && !isNotLoading) {
self.showLoading();
setTimeout(function() {
process();
}, this.options.sortLoading);
} else {
process();
}
}
// 소팅 후, 현재 소팅 상태 캐싱 처리
if(this.options.sortCache) {
this.setOption({
sortIndex: column.index,
sortOrder: column.order
});
}
// 정렬 프로세싱 함수
function process() {
rows.sort(function(a, b) {
return recursiveMultiSort(a.data, b.data, [ column.name ], [ column.order ], 0);
});
// 데이터 초기화 및 입력, 그리고 로딩
self.clear();
self.render(true);
self.emit("sortend", [ column, e ]);
self.hideLoading();
}
}
/**
* @method msort
* Moves a row iat a specified index to the target index.
*
* @param {Array} index
* @param {Array} order "asc" or "desc"
*/
this.msort = function(columns, order_by, isNotLoading) {
if(!this.options.fields || !this.options.msort || this.options.sort) return;
if(!_.typeCheck("array", columns) || !_.typeCheck("array", order_by) || columns.length != order_by.length) return;
if(o_rows == null) o_rows = t_rows;
else t_rows = o_rows;
var self = this,
a_rows = t_rows.slice();
if(this.options.sortLoading && !isNotLoading) {
self.showLoading();
setTimeout(function() {
process();
}, this.options.sortLoading);
} else {
process();
}
// 소팅 후, 현재 소팅 상태 캐싱 처리
if(this.options.sortCache) {
this.setOption({
sortIndex: columns,
sortOrder: order_by
});
}
function process() {
if(columns.length > 0) {
a_rows.sort(function(a, b) {
return recursiveMultiSort(a.data, b.data, columns, order_by, 0);
});
}
// 데이터 초기화 및 입력, 그리고 로딩
rows = a_rows;
self.clear();
self.render(true);
self.emit("msortend");
self.hideLoading();
a_rows = null;
}
}
/**
* @method filter
* Filters columns at a specified to locate rows that contain keywords in the cell value.
*
* @param {Function} callback
*/
this.filter = function(callback) {
if(o_rows == null) o_rows = t_rows;
else t_rows = o_rows;
var a_rows = t_rows.slice(),
f_data = [];
for(var i = 0, len = a_rows.length; i < len; i++) {
var d = a_rows[i].data;
if((typeof(callback) == "function" && callback(d) === true) || !callback) {
f_data.push(d);
}
}
this.update(f_data);
this.emit("filter", [ f_data ]);
a_rows = null;
}
/**
* @method rollback
* Returns filtered rows to the original state.
*/
this.rollback = function() {
this.filter(null);
o_rows = null;
}
/**
* @method clear
* Remove all row elements.
*/
this.clear = function() {
page = 1;
body.uit.removeRows();
body.scroll();
}
/**
* @method clear
* Remove all data
*/
this.reset = function() {
if(this.options.buffer == "vscroll") {
resetVirtualScrollInfo(this);
}
this.clear();
rows = [];
c_rows = [];
t_rows = [];
select_row = null;
}
/**
* @method resize
* Resets the inner scroll and columns of a table.
*/
this.resize = function() {
head.resizeColumns();
head.resize();
head.emit("colresize");
}
/**
* @method scrollWidth
* Sets the scroll based on the width of a table.
*
* @param {Integer} width
*/
this.scrollWidth = function(scrollWidth, isInit) {
// 최초에 스크롤 넓이가 설정되있어야만 메소드 사용 가능
if(this.options.scrollWidth == 0) return;
var width = this.options.width;
if(width > 0) {
var w = (scrollWidth >= width) ? scrollWidth - getScrollBarWidth(this) : width;
$(this.root).outerWidth(w);
} else {
$(this.root).outerWidth(scrollWidth - getScrollBarWidth(this));
}
if(scrollWidth > 0) {
var originWidth = $(this.root).outerWidth();
$(this.root).outerWidth(scrollWidth);
if(isInit) {
$(head.root).outerWidth(originWidth + getScrollBarWidth(this));
$(body.root).outerWidth(originWidth);
reloadScrollWidthResizeBar(1000);
}
$(head.root).parent().css("max-width", scrollWidth);
$(body.root).parent().css("max-width", scrollWidth);
}
}
/**
* @method scrollHeight
* Sets the scroll based on the height of a table.
*
* @param {Integer} height
*/
this.scrollHeight = function(h) {
if(this.options.buffer == "page") return;
$(this.root).find(".body").css("max-height", h + "px");
this.setOption("scrollHeight", h);
setScrollEvent(this, this.options.scrollWidth, h);
}
/**
* @method scrollTop
* Sets the scroll based on the height of a table.
*
* @param {Integer|String} index
* @param {Integer} dist
*/
this.scrollTop = function(index, dist) {
if(this.options.buffer != "vscroll") return;
var $viewport = $(this.root).children(".body");
// 기존의 로우 그릴 수 있는 형태로 계산하기
calculateRows(this, true);
for(var i = 0, len = t_rows.length; i < len; i++) {
var row = t_rows[i];
if(("" + index) == row.index) {
vscroll_info.prev_scroll_top = 0;
vscroll_info.current_row_index = 0;
var scrollTop = i * vscroll_info.height,
scrollHeight = $viewport.height(),
distTop = _.typeCheck("integer", dist) ? dist : 0;
if(scrollTop + distTop > scrollHeight) {
scrollTop += distTop;
}
$viewport.scrollTop(scrollTop);
this.clear();
this.next();
break;
}
}
}
/**
* @deprecated
* @method height
* Sets the scroll based on the height of a table.
*
* @param {Integer} height
*/
this.height = function(h) {
this.scrollHeight(h);
}
/**
* @method size
* Gets the size of all the rows of a table.
*
* @return {Integer} size
*/
this.size = function() { // 차후 수정 (컬럼 * 로우 개수 * 바이트)
return rows.length;
}
/**
* @method count
* Gets the number of trows of a table.
*
* @return {Integer} count
*/
this.count = function() {
return rows.length;
}
/**
* @method list
* Gets all the rows of a table.
*
* @return {Array} rows
*/
this.list = function() {
return rows;
}
/**
* @method listColumn
* Gets all columns.
*
* @return {Array} columns
*/
this.listColumn = function() {
return head.listColumn();
}
/**
* @method listData
* Gets the data of all the rows of a table.
*
* @return {Array} datas
*/
this.listData = function() {
var datas = [];
for(var i = 0; i < rows.length; i++) {
datas.push(rows[i].data);
}
return datas;
}
/**
* @method get
* Gets the row at the specified index.
*
* @param {Integer|String} index
* @return {RowObject} row
*/
this.get = function(index) {
if(index == null) {
return null;
} else {
var row = c_rows[index];
if(!row) {
var keys = iParser.getIndexList(index),
row = c_rows[keys[0]];
for(var i = 1, len = keys.length; i < len; i++) {
if(!row) break;
row = row.children[keys[i]];
}
return row;
} else {
return row;
}
}
}
/**
* @method getAll
* Gets all rows of at the specified index including child rows.
*
* @param {Integer} index
* @return {Array} rows
*/
this.getAll = function(index, _result) {
var row = this.get(index);
if(row != null) {
if(!_.typeCheck("array", _result)) {
_result = [ row ];
}
for(var i = 0; i < row.children.length; i++) {
var child = row.children[i];
_result.push(child);
if(child.children.length > 0) {
return this.getAll(child.index, _result);
}
}
}
return _result;
}
/**
* @method getColumn
* Gets the column at the specified index.
*
* @param {"Integer"/"String"} key index or column key
* @return {ColumnObject} column
*/
this.getColumn = function(index) {
return head.getColumn(index);
}
/**
* @method getData
* Gets the data at the specified index.
*
* @param {"Integer"/"String"} key index
* @return {ColumnObject} data
*/
this.getData = function(index) {
var row = this.get(index);
return (row) ? row.data : null;
}
/**
* @method showColumn
* Shows the column index (or column name).
*
* @param {"Integer"/"String"} key index or column name
*/
this.showColumn = function(index) {
head.showColumn(index);
}
/**
* @method hideColumn
* Hides the column index (or column name).
*
* @param {"Integer"/"String"} key index or column name
*/
this.hideColumn = function(index) {
head.hideColumn(index);
}
/**
* @method initColumns
* It is possible to determine the index or name of the column to be shown in an array.
*
* @param {"Integer"/"String"} key index or column name
*/
this.initColumns = function(keys) {
head.initColumns(keys);
body.initColumns(keys);
head.emit("colresize");
}
/**
* @method showColumnMenu
* Shows the Show/Hide Column menu at specified coordinates.
*
* @param {Integer} x
* @param {Integer} y
*/
this.showColumnMenu = function(x, y) {
head.showColumnMenu(x, y);
}
/**
* @method hideColumnMenu
* Hides the Show/Hide Column menu.
*/
this.hideColumnMenu = function() {
head.hideColumnMenu();
}
/**
* @method toggleColumnMenu
* Shows or hides the Show/Hide Column menu.
*
* @param {Integer} x
* @param {Integer} y
*/
this.toggleColumnMenu = function(x, y) {
head.toggleColumnMenu(x, y);
}
/**
* @method showExpand
* Shows the extended row area of a specified index.
*
* @param {Integer} index
*/
this.showExpand = function(index, obj) {
body.showExpand(index, obj);
}
/**
* @method hideExpand
* Hides the extended row area of a specified index.
*/
this.hideExpand = function(index) {
if(index) body.hideExpand(index);
else body.hideExpand();
}
/**
* @method getExpand
* Get a row in which the extended area is currently activated.
*
* @return {RowObject} row
*/
this.getExpand = function() {
return body.getExpand();
}
/**
* @method showLoading
* Shows the loading screen for the specified delay time.
*
* @param {Integer} delay
*/
this.showLoading = function(delay) {
if(!ui_modal || is_loading) return;
ui_modal.show();
is_loading = true;
if(delay > 0) {
var self = this;
setTimeout(function() {
self.hideLoading();
}, delay);
}
}
/**
* @method hideLoading
* Hides the loading screen.
*/
this.hideLoading = function() {
if(!ui_modal || !is_loading) return;
ui_modal.hide();
is_loading = false;
}
/**
* @method isLoading
*/
this.isLoading = function() {
return is_loading;
}
/**
* @method setCsv
* Updates a table using a CVS string.
*/
this.setCsv = function(csv) {
var opts = this.options;
if(!opts.fields && !opts.csv) return;
var fields = _.getCsvFields(opts.fields, opts.csv),
csvNumber = (opts.csvNumber) ? _.getCsvFields(opts.fields, opts.csvNumber) : null;
this.update(_.csvToData(fields, csv, csvNumber));
}
/**
* @method setCsvFile
* Updates a table using a CVS file.
*/
this.setCsvFile = function(file) {
if(!this.options.fields && !this.options.csv) return;
var self = this;
_.fileToCsv(file, function(csv) {
self.setCsv(csv);
});
}
/**
* @method getCsv
* Gets the data of a table as a CSV string.
*
* @param {Boolean} isTree
* @return {String} csv
*/
this.getCsv = function() {
if(!this.options.fields && !this.options.csv) return;
var fields = _.getCsvFields(this.options.fields, this.options.csv),
len = (rows.length > this.options.csvCount) ? this.options.csvCount : rows.length;
return _.dataToCsv2({
fields: fields,
rows: this.listData(),
count: len,
names: this.options.csvNames
});
}
/**
* @method getCsvBase64
* Gets the data of a table as a CSV string encoded as base64.
*
* @param {Boolean} isTree
* @return {String} base64
*/
this.getCsvBase64 = function() {
if(!this.options.fields && !this.options.csv) return;
return _.csvToBase64(this.getCsv());
}
/**
* @method downloadCsv
* Downloads the data of a table as a CSV file.
*
* @param {String} name
* @param {Boolean} isTree
*/
this.downloadCsv = function(name) {
if(_.typeCheck("string", name)) {
name = name.split(".")[0];
}
var a = document.createElement('a');
a.download = (name) ? name + ".csv" : "table.csv";
a.href = this.getCsvBase64();
document.body.appendChild(a);
a.click();
a.parentNode.removeChild(a);
}
/**
* @method rowFunc
* Ir is possible to use a function for all row data applicable to the column (or column name) of a specified column (or column name). Currently only SUM and AVG are supported.
*
* @param {"sum"/"svg"} funcType
* @param {Integer} columnIndex
* @param {Function} callback
*/
this.rowFunc = function(type, index, callback) {
if(!this.options.fields) return;
var isCallback = (typeof(callback) == "function") ? true : false;
var result = 0,
count = (isCallback) ? 0 : rows.length,
column = head.getColumn(index);
if(column.name) {
for(var i = 0; i < rows.length; i++) {
var data = rows[i].data,
value = data[column.name];
if(!isNaN(value)) {
if(isCallback) {
if(callback(rows[i])) {
result += value;
count++;
}
} else {
result += value;
}
}
}
}
// 현재는 합계와 평균만 지원함
if(type == "sum") return result;
else if(type == "avg") return result / count;
return null;
}
/**
* @method getPage
* Gets the current page of a table.
*
* @return {Integer} page
*/
this.getPage = function() {
return page - 1;
}
/**
* @method activeIndex
* Gets the index of a row that is activated in an extended/modified/selected state.
*
* @return {Integer} index
*/
this.activeIndex = function() {
if(!select_row) return null;
return select_row.index;
}
}
UI.setup = function() {
return {
/**
* @cfg {Array} [fields=null]
* Sets the name of columns in the order of being displayed on the table screen.
*/
fields: null,
/**
* @cfg {Array} [csv=null]
* Sets the column key shown when converted to a CSV string.
*/
csv: null,
/**
* @cfg {Array} [csvNames=null]
* Sets the name of a column shown when converting to a CSV string, which must be defined in the same order as the CSV option.
*/
csvNames: null,
/**
* @cfg {Array} [csvNumber=null]
* Sets the column key to be changed to a number form when converted to a CSV string.
*/
csvNumber: null,
/**
* @cfg {Array} [csvCount=10000]
* Sets the maximum number of rows when creating a CSV string.
*/
csvCount: 10000,
/**
* @cfg {Array} data
* Sets the initial row list of a table.
*/
data: [],
/**
* @cfg {Array} rows
* Sets the initial row list of a table (@Deprecated).
*/
rows: null, // @Deprecated
/**
* @cfg {Boolean/Array} [colshow=false]
* Sets a column index shown when the Show/Hide Column menu is enabled.
*/
colshow: false,
/**
* @cfg {Boolean} [expand=false]
* Determines whether to use an extended row area.
*/
expand: false,
/**
* @cfg {Boolean} [expandEvent=true]
* Shows the extended area automatically when clicking on a row.
*/
expandEvent: true,
/**
* @cfg {Boolean} [resize=false]
* Determines whether to use the column resizing function.
*/
resize: false,
/**
* @cfg {Integer} [rowHeight=26]
* Sets the reference height of a body area when using a table scroll.
*/
rowHeight: 26,
/**
* @cfg {Integer} [scrollHeight=200]
* Sets the reference height of a body area when using a table scroll.
*/
scrollHeight: 200,
/**
* @cfg {Integer} [scrollWidth=0]
* Sets the reference width of a body area when using a table scroll.
*/
scrollWidth: 0,
/**
* @cfg {Integer} [width=0]
* Sets the area of a table.
*/
width: 0,
/**
* @cfg {String} [buffer='scroll'/'page'/'s-page'/'vscroll']
* Sets the buffer type of a table.
*/
buffer: "scroll",
/**
* @cfg {Integer} [bufferCount=100]
* Sets the number of rows per page.
*/
bufferCount: 100,
/**
* @cfg {Boolean/Array} [msort=false]
* Determines whether to use the table sort function.
*/
msort: false,
/**
* @cfg {Boolean/Array} [sort=false]
* Determines whether to use the table sort function.
*/
sort: false,
/**
* @cfg {Boolean} [sortLoading=false]
* Determines whether to show the loading screen when sorting a table.
*/
sortLoading: false,
/**
* @cfg {Boolean} [sortCache=false]
* Configures settings to ensure that the sort state can be maintained even when the table is updated.
*/
sortCache: false,
/**
* @cfg {Integer} [sortIndex=null]
* Determines whether to use the table sort function.
*/
sortIndex: null,
/**
* @cfg {String} [sortOrder="asc"]
* Determines whether to use the table sort function.
*/
sortOrder: "asc",
/**
* @cfg {Boolean} [sortEvent=true]
* Determines whether to use the sort function when you click on a column.
*/
sortEvent: true,
/**
* @cfg {Boolean} [xssFilter=false]
* Activate the xss filter to set the column value.
*/
xssFilter: false,
/**
* @cfg {Boolean} [animate=false]
* @deprecated
*/
animate: false
}
}
/**
* @event select
* Event that occurs when a row is selected (@Deprecated)
*
* @param {RowObject) row
* @param {EventObject} e The event object
*/
/**
* @event click
* Event that occurs when a row is clicked
*
* @param {RowObject) row
* @param {EventObject} e The event object
*/
/**
* @event dblclick
* Event that occurs when a row is double clicked
*
* @param {RowObject) row
* @param {EventObject} e The event object
*/
/**
* @event sort
* Event that occurs when the table is sorted.
*
* @param {ColumnObject) column
* @param {EventObject} e The event object
*/
/**
* @event scroll
* Event that occurs when the scroll of a table is located at the lowermost position.
*
* @param {EventObject} e The event object
*/
* @event rowmenu
* Event that occurs when a row is right clicked.
*
* @param {RowObject) row
* @param {EventObject} e The event object
*/
/**
* @event colclick
* Event that occurs when a column is clicked.
*
* @param {ColumnObject) column
* @param {EventObject} e The event object
*/
/**
* @event colshow
* Event that occurs when shown column is selected.
*
* @param {ColumnObject) column
* @param {EventObject} e The event object
*/
/**
* @event colhide
* Event that occurs when hidden column is selected.
*
* @param {ColumnObject) column
* @param {EventObject} e The event object
*/
/**
* @event colresize
* Event that occurs when the column resizing is activated.
*
* @param {ColumnObject) column
* @param {EventObject} e The event object
*/
/**
* @event expand
* Event that occurs when the extended row area is enabled.
*
* @param {RowObject) row
* @param {EventObject} e The event object
*/
/**
* @event expandend
* Event that occurs when the extended row area is disabled.
*
* @param {RowObject) row
* @param {EventObject} e The event object
*/
return UI;
});