jsgrid.core.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424
  1. (function(window, $, undefined) {
  2. var JSGRID = "JSGrid",
  3. JSGRID_DATA_KEY = JSGRID,
  4. JSGRID_ROW_DATA_KEY = "JSGridItem",
  5. JSGRID_EDIT_ROW_DATA_KEY = "JSGridEditRow",
  6. SORT_ORDER_ASC = "asc",
  7. SORT_ORDER_DESC = "desc",
  8. FIRST_PAGE_PLACEHOLDER = "{first}",
  9. PAGES_PLACEHOLDER = "{pages}",
  10. PREV_PAGE_PLACEHOLDER = "{prev}",
  11. NEXT_PAGE_PLACEHOLDER = "{next}",
  12. LAST_PAGE_PLACEHOLDER = "{last}",
  13. PAGE_INDEX_PLACEHOLDER = "{pageIndex}",
  14. PAGE_COUNT_PLACEHOLDER = "{pageCount}",
  15. ITEM_COUNT_PLACEHOLDER = "{itemCount}",
  16. EMPTY_HREF = "javascript:void(0);";
  17. var getOrApply = function(value, context) {
  18. if($.isFunction(value)) {
  19. return value.apply(context, $.makeArray(arguments).slice(2));
  20. }
  21. return value;
  22. };
  23. var defaultController = {
  24. loadData: $.noop,
  25. insertItem: $.noop,
  26. updateItem: $.noop,
  27. deleteItem: $.noop
  28. };
  29. function Grid(element, config) {
  30. var $element = $(element);
  31. $element.data(JSGRID_DATA_KEY, this);
  32. this._container = $element;
  33. this.data = [];
  34. this.fields = [];
  35. this._editingRow = null;
  36. this._sortField = null;
  37. this._sortOrder = SORT_ORDER_ASC;
  38. this._firstDisplayingPage = 1;
  39. this._init(config);
  40. this.render();
  41. }
  42. Grid.prototype = {
  43. width: "auto",
  44. height: "auto",
  45. updateOnResize: true,
  46. rowClass: $.noop,
  47. rowRenderer: null,
  48. rowClick: function(args) {
  49. if(this.editing) {
  50. this.editItem($(args.event.target).closest("tr"));
  51. }
  52. },
  53. rowDoubleClick: $.noop,
  54. noDataContent: "Not found",
  55. noDataRowClass: "jsgrid-nodata-row",
  56. heading: true,
  57. headerRowRenderer: null,
  58. headerRowClass: "jsgrid-header-row",
  59. filtering: false,
  60. filterRowRenderer: null,
  61. filterRowClass: "jsgrid-filter-row",
  62. inserting: false,
  63. insertRowRenderer: null,
  64. insertRowClass: "jsgrid-insert-row",
  65. editing: false,
  66. editRowRenderer: null,
  67. editRowClass: "jsgrid-edit-row",
  68. confirmDeleting: true,
  69. deleteConfirm: "Are you sure?",
  70. selecting: true,
  71. selectedRowClass: "jsgrid-selected-row",
  72. oddRowClass: "jsgrid-row",
  73. evenRowClass: "jsgrid-alt-row",
  74. sorting: false,
  75. sortableClass: "jsgrid-header-sortable",
  76. sortAscClass: "jsgrid-header-sort jsgrid-header-sort-asc",
  77. sortDescClass: "jsgrid-header-sort jsgrid-header-sort-desc",
  78. paging: false,
  79. pagerContainer: null,
  80. pageIndex: 1,
  81. pageSize: 20,
  82. pageButtonCount: 15,
  83. pagerFormat: "Pages: {first} {prev} {pages} {next} {last}    {pageIndex} of {pageCount}",
  84. pagePrevText: "Prev",
  85. pageNextText: "Next",
  86. pageFirstText: "First",
  87. pageLastText: "Last",
  88. pageNavigatorNextText: "...",
  89. pageNavigatorPrevText: "...",
  90. pagerContainerClass: "jsgrid-pager-container",
  91. pagerClass: "jsgrid-pager",
  92. pagerNavButtonClass: "jsgrid-pager-nav-button",
  93. pagerNavButtonInactiveClass: "jsgrid-pager-nav-inactive-button",
  94. pageClass: "jsgrid-pager-page",
  95. currentPageClass: "jsgrid-pager-current-page",
  96. customLoading: false,
  97. pageLoading: false,
  98. autoload: false,
  99. controller: defaultController,
  100. loadIndication: true,
  101. loadIndicationDelay: 500,
  102. loadMessage: "Please, wait...",
  103. loadShading: true,
  104. invalidMessage: "Invalid data entered!",
  105. invalidNotify: function(args) {
  106. var messages = $.map(args.errors, function(error) {
  107. return error.message || null;
  108. });
  109. window.alert([this.invalidMessage].concat(messages).join("\n"));
  110. },
  111. onRefreshing: $.noop,
  112. onRefreshed: $.noop,
  113. onItemDeleting: $.noop,
  114. onItemDeleted: $.noop,
  115. onItemInserting: $.noop,
  116. onItemInserted: $.noop,
  117. onItemEditing: $.noop,
  118. onItemUpdating: $.noop,
  119. onItemUpdated: $.noop,
  120. onItemInvalid: $.noop,
  121. onDataLoading: $.noop,
  122. onDataLoaded: $.noop,
  123. onOptionChanging: $.noop,
  124. onOptionChanged: $.noop,
  125. onError: $.noop,
  126. invalidClass: "jsgrid-invalid",
  127. containerClass: "jsgrid",
  128. tableClass: "jsgrid-table",
  129. gridHeaderClass: "jsgrid-grid-header",
  130. gridBodyClass: "jsgrid-grid-body",
  131. _init: function(config) {
  132. $.extend(this, config);
  133. this._initLoadStrategy();
  134. this._initController();
  135. this._initFields();
  136. this._attachWindowLoadResize();
  137. this._attachWindowResizeCallback();
  138. },
  139. loadStrategy: function() {
  140. return this.pageLoading
  141. ? new jsGrid.loadStrategies.PageLoadingStrategy(this)
  142. : new jsGrid.loadStrategies.DirectLoadingStrategy(this);
  143. },
  144. _initLoadStrategy: function() {
  145. this._loadStrategy = getOrApply(this.loadStrategy, this);
  146. },
  147. _initController: function() {
  148. this._controller = $.extend({}, defaultController, getOrApply(this.controller, this));
  149. },
  150. loadIndicator: function(config) {
  151. return new jsGrid.LoadIndicator(config);
  152. },
  153. validation: function(config) {
  154. return jsGrid.Validation && new jsGrid.Validation(config);
  155. },
  156. _initFields: function() {
  157. var self = this;
  158. self.fields = $.map(self.fields, function(field) {
  159. if($.isPlainObject(field)) {
  160. var fieldConstructor = (field.type && jsGrid.fields[field.type]) || jsGrid.Field;
  161. field = new fieldConstructor(field);
  162. }
  163. field._grid = self;
  164. return field;
  165. });
  166. },
  167. _attachWindowLoadResize: function() {
  168. $(window).on("load", $.proxy(this._refreshSize, this));
  169. },
  170. _attachWindowResizeCallback: function() {
  171. if(this.updateOnResize) {
  172. $(window).on("resize", $.proxy(this._refreshSize, this));
  173. }
  174. },
  175. _detachWindowResizeCallback: function() {
  176. $(window).off("resize", this._refreshSize);
  177. },
  178. option: function(key, value) {
  179. var optionChangingEventArgs,
  180. optionChangedEventArgs;
  181. if(arguments.length === 1)
  182. return this[key];
  183. optionChangingEventArgs = {
  184. option: key,
  185. oldValue: this[key],
  186. newValue: value
  187. };
  188. this._callEventHandler(this.onOptionChanging, optionChangingEventArgs);
  189. this._handleOptionChange(optionChangingEventArgs.option, optionChangingEventArgs.newValue);
  190. optionChangedEventArgs = {
  191. option: optionChangingEventArgs.option,
  192. value: optionChangingEventArgs.newValue
  193. };
  194. this._callEventHandler(this.onOptionChanged, optionChangedEventArgs);
  195. },
  196. fieldOption: function(field, key, value) {
  197. field = this._normalizeField(field);
  198. if(arguments.length === 2)
  199. return field[key];
  200. field[key] = value;
  201. this._renderGrid();
  202. },
  203. _handleOptionChange: function(name, value) {
  204. this[name] = value;
  205. switch(name) {
  206. case "width":
  207. case "height":
  208. this._refreshSize();
  209. break;
  210. case "rowClass":
  211. case "rowRenderer":
  212. case "rowClick":
  213. case "rowDoubleClick":
  214. case "noDataText":
  215. case "noDataRowClass":
  216. case "noDataContent":
  217. case "selecting":
  218. case "selectedRowClass":
  219. case "oddRowClass":
  220. case "evenRowClass":
  221. this._refreshContent();
  222. break;
  223. case "pageButtonCount":
  224. case "pagerFormat":
  225. case "pagePrevText":
  226. case "pageNextText":
  227. case "pageFirstText":
  228. case "pageLastText":
  229. case "pageNavigatorNextText":
  230. case "pageNavigatorPrevText":
  231. case "pagerClass":
  232. case "pagerNavButtonClass":
  233. case "pageClass":
  234. case "currentPageClass":
  235. case "pagerRenderer":
  236. this._refreshPager();
  237. break;
  238. case "fields":
  239. this._initFields();
  240. this.render();
  241. break;
  242. case "data":
  243. case "editing":
  244. case "heading":
  245. case "filtering":
  246. case "inserting":
  247. case "paging":
  248. this.refresh();
  249. break;
  250. case "loadStrategy":
  251. case "pageLoading":
  252. this._initLoadStrategy();
  253. this.search();
  254. break;
  255. case "pageIndex":
  256. this.openPage(value);
  257. break;
  258. case "pageSize":
  259. this.refresh();
  260. this.search();
  261. break;
  262. case "editRowRenderer":
  263. case "editRowClass":
  264. this.cancelEdit();
  265. break;
  266. case "updateOnResize":
  267. this._detachWindowResizeCallback();
  268. this._attachWindowResizeCallback();
  269. break;
  270. case "invalidNotify":
  271. case "invalidMessage":
  272. break;
  273. default:
  274. this.render();
  275. break;
  276. }
  277. },
  278. destroy: function() {
  279. this._detachWindowResizeCallback();
  280. this._clear();
  281. this._container.removeData(JSGRID_DATA_KEY);
  282. },
  283. render: function() {
  284. this._renderGrid();
  285. return this.autoload ? this.loadData() : $.Deferred().resolve().promise();
  286. },
  287. _renderGrid: function() {
  288. this._clear();
  289. this._container.addClass(this.containerClass)
  290. .css("position", "relative")
  291. .append(this._createHeader())
  292. .append(this._createBody());
  293. this._pagerContainer = this._createPagerContainer();
  294. this._loadIndicator = this._createLoadIndicator();
  295. this._validation = this._createValidation();
  296. this.refresh();
  297. },
  298. _createLoadIndicator: function() {
  299. return getOrApply(this.loadIndicator, this, {
  300. message: this.loadMessage,
  301. shading: this.loadShading,
  302. container: this._container
  303. });
  304. },
  305. _createValidation: function() {
  306. return getOrApply(this.validation, this);
  307. },
  308. _clear: function() {
  309. this.cancelEdit();
  310. clearTimeout(this._loadingTimer);
  311. this._pagerContainer && this._pagerContainer.empty();
  312. this._container.empty()
  313. .css({ position: "", width: "", height: "" });
  314. },
  315. _createHeader: function() {
  316. var $headerRow = this._headerRow = this._createHeaderRow(),
  317. $filterRow = this._filterRow = this._createFilterRow(),
  318. $insertRow = this._insertRow = this._createInsertRow();
  319. var $headerGrid = this._headerGrid = $("<table>").addClass(this.tableClass)
  320. .append($headerRow)
  321. .append($filterRow)
  322. .append($insertRow);
  323. var $header = this._header = $("<div>").addClass(this.gridHeaderClass)
  324. .addClass(this._scrollBarWidth() ? "jsgrid-header-scrollbar" : "")
  325. .append($headerGrid);
  326. return $header;
  327. },
  328. _createBody: function() {
  329. var $content = this._content = $("<tbody>");
  330. var $bodyGrid = this._bodyGrid = $("<table>").addClass(this.tableClass)
  331. .append($content);
  332. var $body = this._body = $("<div>").addClass(this.gridBodyClass)
  333. .append($bodyGrid)
  334. .on("scroll", $.proxy(function(e) {
  335. this._header.scrollLeft(e.target.scrollLeft);
  336. }, this));
  337. return $body;
  338. },
  339. _createPagerContainer: function() {
  340. var pagerContainer = this.pagerContainer || $("<div>").appendTo(this._container);
  341. return $(pagerContainer).addClass(this.pagerContainerClass);
  342. },
  343. _eachField: function(callBack) {
  344. var self = this;
  345. $.each(this.fields, function(index, field) {
  346. if(field.visible) {
  347. callBack.call(self, field, index);
  348. }
  349. });
  350. },
  351. _createHeaderRow: function() {
  352. if($.isFunction(this.headerRowRenderer))
  353. return $(this.headerRowRenderer());
  354. var $result = $("<tr>").addClass(this.headerRowClass);
  355. this._eachField(function(field, index) {
  356. var $th = this._prepareCell("<th>", field, "headercss")
  357. .append(field.headerTemplate ? field.headerTemplate() : "")
  358. .appendTo($result);
  359. if(this.sorting && field.sorting) {
  360. $th.addClass(this.sortableClass)
  361. .on("click", $.proxy(function() {
  362. this.sort(index);
  363. }, this));
  364. }
  365. });
  366. return $result;
  367. },
  368. _prepareCell: function(cell, field, cssprop) {
  369. return $(cell).css("width", field.width)
  370. .addClass((cssprop && field[cssprop]) || field.css)
  371. .addClass(field.align ? ("jsgrid-align-" + field.align) : "");
  372. },
  373. _createFilterRow: function() {
  374. if($.isFunction(this.filterRowRenderer))
  375. return $(this.filterRowRenderer());
  376. var $result = $("<tr>").addClass(this.filterRowClass);
  377. this._eachField(function(field) {
  378. this._prepareCell("<td>", field, "filtercss")
  379. .append(field.filterTemplate ? field.filterTemplate() : "")
  380. .appendTo($result);
  381. });
  382. return $result;
  383. },
  384. _createInsertRow: function() {
  385. if($.isFunction(this.insertRowRenderer))
  386. return $(this.insertRowRenderer());
  387. var $result = $("<tr>").addClass(this.insertRowClass);
  388. this._eachField(function(field) {
  389. this._prepareCell("<td>", field, "insertcss")
  390. .append(field.insertTemplate ? field.insertTemplate() : "")
  391. .appendTo($result);
  392. });
  393. return $result;
  394. },
  395. _callEventHandler: function(handler, eventParams) {
  396. handler.call(this, $.extend(eventParams, {
  397. grid: this
  398. }));
  399. return eventParams;
  400. },
  401. reset: function() {
  402. this._resetSorting();
  403. this._resetPager();
  404. this.refresh();
  405. },
  406. _resetPager: function() {
  407. this._firstDisplayingPage = 1;
  408. this._setPage(1);
  409. },
  410. _resetSorting: function() {
  411. this._sortField = null;
  412. this._sortOrder = SORT_ORDER_ASC;
  413. this._clearSortingCss();
  414. },
  415. refresh: function() {
  416. this._callEventHandler(this.onRefreshing);
  417. this.cancelEdit();
  418. this._refreshHeading();
  419. this._refreshFiltering();
  420. this._refreshInserting();
  421. this._refreshContent();
  422. this._refreshPager();
  423. this._refreshSize();
  424. this._callEventHandler(this.onRefreshed);
  425. },
  426. _refreshHeading: function() {
  427. this._headerRow.toggle(this.heading);
  428. },
  429. _refreshFiltering: function() {
  430. this._filterRow.toggle(this.filtering);
  431. },
  432. _refreshInserting: function() {
  433. this._insertRow.toggle(this.inserting);
  434. },
  435. _refreshContent: function() {
  436. var $content = this._content;
  437. $content.empty();
  438. if(!this.data.length) {
  439. $content.append(this._createNoDataRow());
  440. return this;
  441. }
  442. var indexFrom = this._loadStrategy.firstDisplayIndex();
  443. var indexTo = this._loadStrategy.lastDisplayIndex();
  444. for(var itemIndex = indexFrom; itemIndex < indexTo; itemIndex++) {
  445. var item = this.data[itemIndex];
  446. $content.append(this._createRow(item, itemIndex));
  447. }
  448. },
  449. _createNoDataRow: function() {
  450. var noDataContent = getOrApply(this.noDataContent, this);
  451. var amountOfFields = 0;
  452. this._eachField(function() {
  453. amountOfFields++;
  454. });
  455. return $("<tr>").addClass(this.noDataRowClass)
  456. .append($("<td>").attr("colspan", amountOfFields).append(noDataContent));
  457. },
  458. _createNoDataContent: function() {
  459. return $.isFunction(this.noDataRenderer)
  460. ? this.noDataRenderer()
  461. : this.noDataText;
  462. },
  463. _createRow: function(item, itemIndex) {
  464. var $result;
  465. if($.isFunction(this.rowRenderer)) {
  466. $result = $(this.rowRenderer(item, itemIndex));
  467. } else {
  468. $result = $("<tr>");
  469. this._renderCells($result, item);
  470. }
  471. $result.addClass(this._getRowClasses(item, itemIndex))
  472. .data(JSGRID_ROW_DATA_KEY, item)
  473. .on("click", $.proxy(function(e) {
  474. this.rowClick({
  475. item: item,
  476. itemIndex: itemIndex,
  477. event: e
  478. });
  479. }, this))
  480. .on("dblclick", $.proxy(function(e) {
  481. this.rowDoubleClick({
  482. item: item,
  483. itemIndex: itemIndex,
  484. event: e
  485. });
  486. }, this));
  487. if(this.selecting) {
  488. this._attachRowHover($result);
  489. }
  490. return $result;
  491. },
  492. _getRowClasses: function(item, itemIndex) {
  493. var classes = [];
  494. classes.push(((itemIndex + 1) % 2) ? this.oddRowClass : this.evenRowClass);
  495. classes.push(getOrApply(this.rowClass, this, item, itemIndex));
  496. return classes.join(" ");
  497. },
  498. _attachRowHover: function($row) {
  499. var selectedRowClass = this.selectedRowClass;
  500. $row.hover(function() {
  501. $(this).addClass(selectedRowClass);
  502. },
  503. function() {
  504. $(this).removeClass(selectedRowClass);
  505. }
  506. );
  507. },
  508. _renderCells: function($row, item) {
  509. this._eachField(function(field) {
  510. $row.append(this._createCell(item, field));
  511. });
  512. return this;
  513. },
  514. _createCell: function(item, field) {
  515. var $result;
  516. var fieldValue = this._getItemFieldValue(item, field);
  517. if($.isFunction(field.cellRenderer)) {
  518. $result = $(field.cellRenderer(fieldValue, item));
  519. } else {
  520. $result = $("<td>").append(field.itemTemplate ? field.itemTemplate(fieldValue, item) : fieldValue);
  521. }
  522. return this._prepareCell($result, field);
  523. },
  524. _getItemFieldValue: function(item, field) {
  525. var props = field.name.split('.');
  526. var result = item[props.shift()];
  527. while(result && props.length) {
  528. result = result[props.shift()];
  529. }
  530. return result;
  531. },
  532. _setItemFieldValue: function(item, field, value) {
  533. var props = field.name.split('.');
  534. var current = item;
  535. var prop = props[0];
  536. while(current && props.length) {
  537. item = current;
  538. prop = props.shift();
  539. current = item[prop];
  540. }
  541. if(!current) {
  542. while(props.length) {
  543. item = item[prop] = {};
  544. prop = props.shift();
  545. }
  546. }
  547. item[prop] = value;
  548. },
  549. sort: function(field, order) {
  550. if($.isPlainObject(field)) {
  551. order = field.order;
  552. field = field.field;
  553. }
  554. this._clearSortingCss();
  555. this._setSortingParams(field, order);
  556. this._setSortingCss();
  557. return this._loadStrategy.sort();
  558. },
  559. _clearSortingCss: function() {
  560. this._headerRow.find("th")
  561. .removeClass(this.sortAscClass)
  562. .removeClass(this.sortDescClass);
  563. },
  564. _setSortingParams: function(field, order) {
  565. field = this._normalizeField(field);
  566. order = order || ((this._sortField === field) ? this._reversedSortOrder(this._sortOrder) : SORT_ORDER_ASC);
  567. this._sortField = field;
  568. this._sortOrder = order;
  569. },
  570. _normalizeField: function(field) {
  571. if($.isNumeric(field)) {
  572. return this.fields[field];
  573. }
  574. if(typeof field === "string") {
  575. return $.grep(this.fields, function(f) {
  576. return f.name === field;
  577. })[0];
  578. }
  579. return field;
  580. },
  581. _reversedSortOrder: function(order) {
  582. return (order === SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC);
  583. },
  584. _setSortingCss: function() {
  585. var fieldIndex = $.inArray(this._sortField, $.grep(this.fields, function(f) { return f.visible; }));
  586. this._headerRow.find("th").eq(fieldIndex)
  587. .addClass(this._sortOrder === SORT_ORDER_ASC ? this.sortAscClass : this.sortDescClass);
  588. },
  589. _sortData: function() {
  590. var sortFactor = this._sortFactor(),
  591. sortField = this._sortField;
  592. if(sortField) {
  593. this.data.sort(function(item1, item2) {
  594. return sortFactor * sortField.sortingFunc(item1[sortField.name], item2[sortField.name]);
  595. });
  596. }
  597. },
  598. _sortFactor: function() {
  599. return this._sortOrder === SORT_ORDER_ASC ? 1 : -1;
  600. },
  601. _itemsCount: function() {
  602. return this._loadStrategy.itemsCount();
  603. },
  604. _pagesCount: function() {
  605. var itemsCount = this._itemsCount(),
  606. pageSize = this.pageSize;
  607. return Math.floor(itemsCount / pageSize) + (itemsCount % pageSize ? 1 : 0);
  608. },
  609. _refreshPager: function() {
  610. var $pagerContainer = this._pagerContainer;
  611. $pagerContainer.empty();
  612. if(this.paging) {
  613. $pagerContainer.append(this._createPager());
  614. }
  615. var showPager = this.paging && this._pagesCount() > 1;
  616. $pagerContainer.toggle(showPager);
  617. },
  618. _createPager: function() {
  619. var $result;
  620. if($.isFunction(this.pagerRenderer)) {
  621. $result = $(this.pagerRenderer({
  622. pageIndex: this.pageIndex,
  623. pageCount: this._pagesCount()
  624. }));
  625. } else {
  626. $result = $("<div>").append(this._createPagerByFormat());
  627. }
  628. $result.addClass(this.pagerClass);
  629. return $result;
  630. },
  631. _createPagerByFormat: function() {
  632. var pageIndex = this.pageIndex,
  633. pageCount = this._pagesCount(),
  634. itemCount = this._itemsCount(),
  635. pagerParts = this.pagerFormat.split(" ");
  636. return $.map(pagerParts, $.proxy(function(pagerPart) {
  637. var result = pagerPart;
  638. if(pagerPart === PAGES_PLACEHOLDER) {
  639. result = this._createPages();
  640. } else if(pagerPart === FIRST_PAGE_PLACEHOLDER) {
  641. result = this._createPagerNavButton(this.pageFirstText, 1, pageIndex > 1);
  642. } else if(pagerPart === PREV_PAGE_PLACEHOLDER) {
  643. result = this._createPagerNavButton(this.pagePrevText, pageIndex - 1, pageIndex > 1);
  644. } else if(pagerPart === NEXT_PAGE_PLACEHOLDER) {
  645. result = this._createPagerNavButton(this.pageNextText, pageIndex + 1, pageIndex < pageCount);
  646. } else if(pagerPart === LAST_PAGE_PLACEHOLDER) {
  647. result = this._createPagerNavButton(this.pageLastText, pageCount, pageIndex < pageCount);
  648. } else if(pagerPart === PAGE_INDEX_PLACEHOLDER) {
  649. result = pageIndex;
  650. } else if(pagerPart === PAGE_COUNT_PLACEHOLDER) {
  651. result = pageCount;
  652. } else if(pagerPart === ITEM_COUNT_PLACEHOLDER) {
  653. result = itemCount;
  654. }
  655. return $.isArray(result) ? result.concat([" "]) : [result, " "];
  656. }, this));
  657. },
  658. _createPages: function() {
  659. var pageCount = this._pagesCount(),
  660. pageButtonCount = this.pageButtonCount,
  661. firstDisplayingPage = this._firstDisplayingPage,
  662. pages = [];
  663. if(firstDisplayingPage > 1) {
  664. pages.push(this._createPagerPageNavButton(this.pageNavigatorPrevText, this.showPrevPages));
  665. }
  666. for(var i = 0, pageNumber = firstDisplayingPage; i < pageButtonCount && pageNumber <= pageCount; i++, pageNumber++) {
  667. pages.push(pageNumber === this.pageIndex
  668. ? this._createPagerCurrentPage()
  669. : this._createPagerPage(pageNumber));
  670. }
  671. if((firstDisplayingPage + pageButtonCount - 1) < pageCount) {
  672. pages.push(this._createPagerPageNavButton(this.pageNavigatorNextText, this.showNextPages));
  673. }
  674. return pages;
  675. },
  676. _createPagerNavButton: function(text, pageIndex, isActive) {
  677. return this._createPagerButton(text, this.pagerNavButtonClass + (isActive ? "" : " " + this.pagerNavButtonInactiveClass),
  678. isActive ? function() { this.openPage(pageIndex); } : $.noop);
  679. },
  680. _createPagerPageNavButton: function(text, handler) {
  681. return this._createPagerButton(text, this.pagerNavButtonClass, handler);
  682. },
  683. _createPagerPage: function(pageIndex) {
  684. return this._createPagerButton(pageIndex, this.pageClass, function() {
  685. this.openPage(pageIndex);
  686. });
  687. },
  688. _createPagerButton: function(text, css, handler) {
  689. var $link = $("<a>").attr("href", EMPTY_HREF)
  690. .html(text)
  691. .on("click", $.proxy(handler, this));
  692. return $("<span>").addClass(css).append($link);
  693. },
  694. _createPagerCurrentPage: function() {
  695. return $("<span>")
  696. .addClass(this.pageClass)
  697. .addClass(this.currentPageClass)
  698. .text(this.pageIndex);
  699. },
  700. _refreshSize: function() {
  701. this._refreshHeight();
  702. this._refreshWidth();
  703. },
  704. _refreshWidth: function() {
  705. var $headerGrid = this._headerGrid,
  706. $bodyGrid = this._bodyGrid,
  707. width = this.width;
  708. if(width === "auto") {
  709. $headerGrid.width("auto");
  710. width = $headerGrid.outerWidth();
  711. }
  712. $headerGrid.width("");
  713. $bodyGrid.width("");
  714. this._container.width(width);
  715. width = $headerGrid.outerWidth();
  716. $bodyGrid.width(width);
  717. },
  718. _scrollBarWidth: (function() {
  719. var result;
  720. return function() {
  721. if(result === undefined) {
  722. var $ghostContainer = $("<div style='width:50px;height:50px;overflow:hidden;position:absolute;top:-10000px;left:-10000px;'></div>");
  723. var $ghostContent = $("<div style='height:100px;'></div>");
  724. $ghostContainer.append($ghostContent).appendTo("body");
  725. var width = $ghostContent.innerWidth();
  726. $ghostContainer.css("overflow-y", "auto");
  727. var widthExcludingScrollBar = $ghostContent.innerWidth();
  728. $ghostContainer.remove();
  729. result = width - widthExcludingScrollBar;
  730. }
  731. return result;
  732. };
  733. })(),
  734. _refreshHeight: function() {
  735. var container = this._container,
  736. pagerContainer = this._pagerContainer,
  737. height = this.height,
  738. nonBodyHeight;
  739. container.height(height);
  740. if(height !== "auto") {
  741. height = container.height();
  742. nonBodyHeight = this._header.outerHeight(true);
  743. if(pagerContainer.parents(container).length) {
  744. nonBodyHeight += pagerContainer.outerHeight(true);
  745. }
  746. this._body.outerHeight(height - nonBodyHeight);
  747. }
  748. },
  749. showPrevPages: function() {
  750. var firstDisplayingPage = this._firstDisplayingPage,
  751. pageButtonCount = this.pageButtonCount;
  752. this._firstDisplayingPage = (firstDisplayingPage > pageButtonCount) ? firstDisplayingPage - pageButtonCount : 1;
  753. this._refreshPager();
  754. },
  755. showNextPages: function() {
  756. var firstDisplayingPage = this._firstDisplayingPage,
  757. pageButtonCount = this.pageButtonCount,
  758. pageCount = this._pagesCount();
  759. this._firstDisplayingPage = (firstDisplayingPage + 2 * pageButtonCount > pageCount)
  760. ? pageCount - pageButtonCount + 1
  761. : firstDisplayingPage + pageButtonCount;
  762. this._refreshPager();
  763. },
  764. openPage: function(pageIndex) {
  765. if(pageIndex < 1 || pageIndex > this._pagesCount())
  766. return;
  767. this._setPage(pageIndex);
  768. this._loadStrategy.openPage(pageIndex);
  769. },
  770. _setPage: function(pageIndex) {
  771. var firstDisplayingPage = this._firstDisplayingPage,
  772. pageButtonCount = this.pageButtonCount;
  773. this.pageIndex = pageIndex;
  774. if(pageIndex < firstDisplayingPage) {
  775. this._firstDisplayingPage = pageIndex;
  776. }
  777. if(pageIndex > firstDisplayingPage + pageButtonCount - 1) {
  778. this._firstDisplayingPage = pageIndex - pageButtonCount + 1;
  779. }
  780. },
  781. _controllerCall: function(method, param, isCanceled, doneCallback) {
  782. if(isCanceled)
  783. return $.Deferred().reject().promise();
  784. this._showLoading();
  785. var controller = this._controller;
  786. if(!controller || !controller[method]) {
  787. throw Error("controller has no method '" + method + "'");
  788. }
  789. return $.when(controller[method](param))
  790. .done($.proxy(doneCallback, this))
  791. .fail($.proxy(this._errorHandler, this))
  792. .always($.proxy(this._hideLoading, this));
  793. },
  794. _errorHandler: function() {
  795. this._callEventHandler(this.onError, {
  796. args: $.makeArray(arguments)
  797. });
  798. },
  799. _showLoading: function() {
  800. if(!this.loadIndication)
  801. return;
  802. clearTimeout(this._loadingTimer);
  803. this._loadingTimer = setTimeout($.proxy(function() {
  804. this._loadIndicator.show();
  805. }, this), this.loadIndicationDelay);
  806. },
  807. _hideLoading: function() {
  808. if(!this.loadIndication)
  809. return;
  810. clearTimeout(this._loadingTimer);
  811. this._loadIndicator.hide();
  812. },
  813. search: function(filter) {
  814. this._resetSorting();
  815. this._resetPager();
  816. return this.loadData(filter);
  817. },
  818. loadData: function(filter) {
  819. filter = filter || (this.filtering ? this.getFilter() : {});
  820. $.extend(filter, this._loadStrategy.loadParams(), this._sortingParams());
  821. var args = this._callEventHandler(this.onDataLoading, {
  822. filter: filter
  823. });
  824. return this._controllerCall("loadData", filter, args.cancel, function(loadedData) {
  825. if(!loadedData)
  826. return;
  827. this._loadStrategy.finishLoad(loadedData);
  828. this._callEventHandler(this.onDataLoaded, {
  829. data: loadedData
  830. });
  831. });
  832. },
  833. getFilter: function() {
  834. var result = {};
  835. this._eachField(function(field) {
  836. if(field.filtering) {
  837. this._setItemFieldValue(result, field, field.filterValue());
  838. }
  839. });
  840. return result;
  841. },
  842. _sortingParams: function() {
  843. if(this.sorting && this._sortField) {
  844. return {
  845. sortField: this._sortField.name,
  846. sortOrder: this._sortOrder
  847. };
  848. }
  849. return {};
  850. },
  851. getSorting: function() {
  852. var sortingParams = this._sortingParams();
  853. return {
  854. field: sortingParams.sortField,
  855. order: sortingParams.sortOrder
  856. };
  857. },
  858. clearFilter: function() {
  859. var $filterRow = this._createFilterRow();
  860. this._filterRow.replaceWith($filterRow);
  861. this._filterRow = $filterRow;
  862. return this.search();
  863. },
  864. insertItem: function(item) {
  865. var insertingItem = item || this._getValidatedInsertItem();
  866. if(!insertingItem)
  867. return $.Deferred().reject().promise();
  868. var args = this._callEventHandler(this.onItemInserting, {
  869. item: insertingItem
  870. });
  871. return this._controllerCall("insertItem", insertingItem, args.cancel, function(insertedItem) {
  872. insertedItem = insertedItem || insertingItem;
  873. this._loadStrategy.finishInsert(insertedItem);
  874. this._callEventHandler(this.onItemInserted, {
  875. item: insertedItem
  876. });
  877. });
  878. },
  879. _getValidatedInsertItem: function() {
  880. var item = this._getInsertItem();
  881. return this._validateItem(item, this._insertRow) ? item : null;
  882. },
  883. _getInsertItem: function() {
  884. var result = {};
  885. this._eachField(function(field) {
  886. if(field.inserting) {
  887. this._setItemFieldValue(result, field, field.insertValue());
  888. }
  889. });
  890. return result;
  891. },
  892. _validateItem: function(item, $row) {
  893. var validationErrors = [];
  894. var args = {
  895. item: item,
  896. itemIndex: this._rowIndex($row),
  897. row: $row
  898. };
  899. this._eachField(function(field, index) {
  900. if(!field.validate)
  901. return;
  902. var errors = this._validation.validate($.extend({
  903. value: this._getItemFieldValue(item, field),
  904. rules: field.validate
  905. }, args));
  906. this._setCellValidity($row.children().eq(index), errors);
  907. if(!errors.length)
  908. return;
  909. validationErrors.push.apply(validationErrors,
  910. $.map(errors, function(message) {
  911. return { field: field, message: message };
  912. }));
  913. });
  914. if(!validationErrors.length)
  915. return true;
  916. var invalidArgs = $.extend({
  917. errors: validationErrors
  918. }, args);
  919. this._callEventHandler(this.onItemInvalid, invalidArgs);
  920. this.invalidNotify(invalidArgs);
  921. return false;
  922. },
  923. _setCellValidity: function($cell, errors) {
  924. $cell
  925. .toggleClass(this.invalidClass, !!errors.length)
  926. .attr("title", errors.join("\n"));
  927. },
  928. clearInsert: function() {
  929. var insertRow = this._createInsertRow();
  930. this._insertRow.replaceWith(insertRow);
  931. this._insertRow = insertRow;
  932. this.refresh();
  933. },
  934. editItem: function(item) {
  935. var $row = this.rowByItem(item);
  936. if($row.length) {
  937. this._editRow($row);
  938. }
  939. },
  940. rowByItem: function(item) {
  941. if(item.jquery || item.nodeType)
  942. return $(item);
  943. return this._content.find("tr").filter(function() {
  944. return $.data(this, JSGRID_ROW_DATA_KEY) === item;
  945. });
  946. },
  947. _editRow: function($row) {
  948. if(!this.editing)
  949. return;
  950. var item = $row.data(JSGRID_ROW_DATA_KEY);
  951. var args = this._callEventHandler(this.onItemEditing, {
  952. row: $row,
  953. item: item,
  954. itemIndex: this._itemIndex(item)
  955. });
  956. if(args.cancel)
  957. return;
  958. if(this._editingRow) {
  959. this.cancelEdit();
  960. }
  961. var $editRow = this._createEditRow(item);
  962. this._editingRow = $row;
  963. $row.hide();
  964. $editRow.insertBefore($row);
  965. $row.data(JSGRID_EDIT_ROW_DATA_KEY, $editRow);
  966. },
  967. _createEditRow: function(item) {
  968. if($.isFunction(this.editRowRenderer)) {
  969. return $(this.editRowRenderer(item, this._itemIndex(item)));
  970. }
  971. var $result = $("<tr>").addClass(this.editRowClass);
  972. this._eachField(function(field) {
  973. var fieldValue = this._getItemFieldValue(item, field);
  974. this._prepareCell("<td>", field, "editcss")
  975. .append(field.editTemplate ? field.editTemplate(fieldValue, item) : "")
  976. .appendTo($result);
  977. });
  978. return $result;
  979. },
  980. updateItem: function(item, editedItem) {
  981. if(arguments.length === 1) {
  982. editedItem = item;
  983. }
  984. var $row = item ? this.rowByItem(item) : this._editingRow;
  985. editedItem = editedItem || this._getValidatedEditedItem();
  986. if(!editedItem)
  987. return;
  988. return this._updateRow($row, editedItem);
  989. },
  990. _getValidatedEditedItem: function() {
  991. var item = this._getEditedItem();
  992. return this._validateItem(item, this._getEditRow()) ? item : null;
  993. },
  994. _updateRow: function($updatingRow, editedItem) {
  995. var updatingItem = $updatingRow.data(JSGRID_ROW_DATA_KEY),
  996. updatingItemIndex = this._itemIndex(updatingItem),
  997. previousItem = $.extend(true, {}, updatingItem);
  998. $.extend(true, updatingItem, editedItem);
  999. var args = this._callEventHandler(this.onItemUpdating, {
  1000. row: $updatingRow,
  1001. item: updatingItem,
  1002. itemIndex: updatingItemIndex,
  1003. previousItem: previousItem
  1004. });
  1005. return this._controllerCall("updateItem", updatingItem, args.cancel, function(updatedItem) {
  1006. updatedItem = updatedItem || updatingItem;
  1007. var $updatedRow = this._finishUpdate($updatingRow, updatedItem, updatingItemIndex);
  1008. this._callEventHandler(this.onItemUpdated, {
  1009. row: $updatedRow,
  1010. item: updatedItem,
  1011. itemIndex: updatingItemIndex,
  1012. previousItem: previousItem
  1013. });
  1014. });
  1015. },
  1016. _rowIndex: function(row) {
  1017. return this._content.children().index($(row));
  1018. },
  1019. _itemIndex: function(item) {
  1020. return $.inArray(item, this.data);
  1021. },
  1022. _finishUpdate: function($updatingRow, updatedItem, updatedItemIndex) {
  1023. this.cancelEdit();
  1024. this.data[updatedItemIndex] = updatedItem;
  1025. var $updatedRow = this._createRow(updatedItem, updatedItemIndex);
  1026. $updatingRow.replaceWith($updatedRow);
  1027. return $updatedRow;
  1028. },
  1029. _getEditedItem: function() {
  1030. var result = {};
  1031. this._eachField(function(field) {
  1032. if(field.editing) {
  1033. this._setItemFieldValue(result, field, field.editValue());
  1034. }
  1035. });
  1036. return result;
  1037. },
  1038. cancelEdit: function() {
  1039. if(!this._editingRow)
  1040. return;
  1041. this._getEditRow().remove();
  1042. this._editingRow.show();
  1043. this._editingRow = null;
  1044. },
  1045. _getEditRow: function() {
  1046. return this._editingRow.data(JSGRID_EDIT_ROW_DATA_KEY);
  1047. },
  1048. deleteItem: function(item) {
  1049. var $row = this.rowByItem(item);
  1050. if(!$row.length)
  1051. return;
  1052. if(this.confirmDeleting && !window.confirm(getOrApply(this.deleteConfirm, this, $row.data(JSGRID_ROW_DATA_KEY))))
  1053. return;
  1054. return this._deleteRow($row);
  1055. },
  1056. _deleteRow: function($row) {
  1057. var deletingItem = $row.data(JSGRID_ROW_DATA_KEY),
  1058. deletingItemIndex = this._itemIndex(deletingItem);
  1059. var args = this._callEventHandler(this.onItemDeleting, {
  1060. row: $row,
  1061. item: deletingItem,
  1062. itemIndex: deletingItemIndex
  1063. });
  1064. return this._controllerCall("deleteItem", deletingItem, args.cancel, function() {
  1065. this._loadStrategy.finishDelete(deletingItem, deletingItemIndex);
  1066. this._callEventHandler(this.onItemDeleted, {
  1067. row: $row,
  1068. item: deletingItem,
  1069. itemIndex: deletingItemIndex
  1070. });
  1071. });
  1072. }
  1073. };
  1074. $.fn.jsGrid = function(config) {
  1075. var args = $.makeArray(arguments),
  1076. methodArgs = args.slice(1),
  1077. result = this;
  1078. this.each(function() {
  1079. var $element = $(this),
  1080. instance = $element.data(JSGRID_DATA_KEY),
  1081. methodResult;
  1082. if(instance) {
  1083. if(typeof config === "string") {
  1084. methodResult = instance[config].apply(instance, methodArgs);
  1085. if(methodResult !== undefined && methodResult !== instance) {
  1086. result = methodResult;
  1087. return false;
  1088. }
  1089. } else {
  1090. instance._detachWindowResizeCallback();
  1091. instance._init(config);
  1092. instance.render();
  1093. }
  1094. } else {
  1095. new Grid($element, config);
  1096. }
  1097. });
  1098. return result;
  1099. };
  1100. var fields = {};
  1101. var setDefaults = function(config) {
  1102. var componentPrototype;
  1103. if($.isPlainObject(config)) {
  1104. componentPrototype = Grid.prototype;
  1105. } else {
  1106. componentPrototype = fields[config].prototype;
  1107. config = arguments[1] || {};
  1108. }
  1109. $.extend(componentPrototype, config);
  1110. };
  1111. var locales = {};
  1112. var locale = function(lang) {
  1113. var localeConfig = $.isPlainObject(lang) ? lang : locales[lang];
  1114. if(!localeConfig)
  1115. throw Error("unknown locale " + lang);
  1116. setLocale(jsGrid, localeConfig);
  1117. };
  1118. var setLocale = function(obj, localeConfig) {
  1119. $.each(localeConfig, function(field, value) {
  1120. if($.isPlainObject(value)) {
  1121. setLocale(obj[field] || obj[field[0].toUpperCase() + field.slice(1)], value);
  1122. return;
  1123. }
  1124. if(obj.hasOwnProperty(field)) {
  1125. obj[field] = value;
  1126. } else {
  1127. obj.prototype[field] = value;
  1128. }
  1129. });
  1130. };
  1131. window.jsGrid = {
  1132. Grid: Grid,
  1133. fields: fields,
  1134. setDefaults: setDefaults,
  1135. locales: locales,
  1136. locale: locale
  1137. };
  1138. }(window, jQuery));