'+html+'';
//now it needs top and tailing and the row flagged as in detail mode
//we need to set form type/data-type/autofocus
html = html.replace('';
//set current scroll
////html = html.replace(' g.gridSection.scrollTop + g.gridSection.offsetHeight){
//push up just enough to get the bottom on
adj = (item.y + item.height) - (g.gridSection.scrollTop + g.gridSection.offsetHeight)
//but what if the adj is too much and pushes the top off....
if(item.y < (g.gridSection.scrollTop + adj) ){
adj += item.y - (g.gridSection.scrollTop + adj);
}
}else{
//ntg
}
//var top = item.y + item.height - g.viewportTop - g.gridSection.offsetHeight;
///if(top > 0) g.gridSection.scrollTop += top;
if(adj) g.gridSection.scrollTop += adj;
}
})
}
//if its a grow grid the count will never change as the grid never scrolls
g.selectChange = false;
}
}
/**
* Reposition the editor when it has resized
* Called from render to reposition a new editor on a new row 'new' - vertical and horizontal
* Called from toggle to reposition same row different editor 'toggle' vertical
* Called from edit next cell to reposition on a tab 'cell' horizontal
* Called from resize to reposition on a resize... 'resize' vertical
* YOU MUST NOT READ DOM - CAN ONLY WRITE DOM
* @param {GridConfig} g
* @param {HTMLElement} row
* @param {boolean} focusRequired
* @param {boolean} scrolling
*/
function positionEditor(g, row, focusRequired, scrolling) {
// we need to make sure that the editor can be fully on screen
// so we scroll up the row if required
// but what is the max available height - if grow it is infinite
// if not grow its limited to available space???
// we only need to move up ..... and no row refill
// the adjustment required to get the bottom of the editor on screen
// but what if the editor is too large to fit
// really this is the devs fault they should make the form scrollable and we should max height a form....
// we restrict the offset to keep the top on screen.....
var node = g.vdom.find(findIndexHelper, {keyField:'key', key:row.dataset.key});
var height = node.height;
var y = node.y;
var btm = y + height;
var vpt = g.lastScrollTop || 0;
var vpb = vpt + g.gridSectionViewportHeight;//the real viewportHeight;
var vadj = 0;
if (y < vpt) {
//force scroll down
vadj = vpt - y;
} else if (btm > vpb) {
//force scroll up
vadj = btm - vpb
} else {
//were good
}
if (!scrolling && vadj) {
g.gridSection.scrollTop = g.lastScrollTop + vadj;
}
//focus a control
if(focusRequired) {
//figure out the next focus target - make sure that the scroll position is in sync.....
var form = g.editConfig.row.querySelector('form');
var focusField = g.editConfig.editField;
g.editConfig.next = g.editConfig.next || Focus.findFirstFocusable(focusField ? form[focusField] || form : form) || g.editConfig.activeElement;
Focus.focusLater(g.editConfig.next);
// really if we move from one edit row to the next and its the first editable cell and we are not at scroll 0
// then we should force it
DOMMoveTracker.doAction(g.gridHScrollbar,'scroll-force');
}
//start resizer - chrome uses RO
if(!g.gridRowSizerActive) {
g.gridRowSizerActive = true;
if ($.support.browser.chrome) {
g.gridRowResizer.observe(row);
} else {
System.addPreRenderNextTask(getRowSizes, g);
}
}
}
/**
* focus a cell making sure it is in view before focusing
* @param {GridConfig} g
* @param {HTMLElement} el
*/
function focusCell(g, el){
//horizontally scroll the target cell to be in view
//only if in the basic editor, the details and external editors dont scroll
//the next target
var form = g.editConfig.row.querySelector('form');
var focusField = g.editConfig.editField;
g.editConfig.next = el || g.editConfig.next ||Focus.findFirstFocusable(focusField ? form[focusField] || form : form) || g.editConfig.activeElement;;
//the current target
g.editConfig.activeElement = g.editConfig.activeElement || document.activeElement;
if(g.editConfig.editor === 'basic') {
var root = g.editConfig.next.closest('grid-cells');
var grps = root.querySelectorAll('grid-cellgroup');
var grp1 = grps[0];
var grp2 = grps[1];
var isLocked = grps.length ? grp1.contains(g.editConfig.next) : true;
var wasLocked = grps.length ? grp1.contains(g.editConfig.activeElement) : true;
if (isLocked) {
// if we tab from the unlocked to the locked we may 'skip' over some elements
// so we reset the scroll pos
if (!wasLocked) root.scrollLeft = 0;
} else {
//seems in some cases the devs are setting buttons in a cell to be tabbable so they come up as targets
//so we need to look for the input control in case its a custom cell and the editcell if its not a control
var rectCtl = (g.editConfig.next.closest('.input-control,.editcell') || form).getBoundingClientRect();
var rectRoot = root.getBoundingClientRect();
var rectGroup = grp1.getBoundingClientRect();
if (rectGroup.right > rectCtl.left) {
root.scrollLeft += rectCtl.left - rectGroup.right;
DOMMoveTracker.doAction(g.gridHScrollbar, 'scroll-force', root.scrollLeft);
} else if (rectRoot.right < rectCtl.right) {
root.scrollLeft += rectCtl.right - rectRoot.right + 12;
DOMMoveTracker.doAction(g.gridHScrollbar, 'scroll-force', root.scrollLeft);
}
}
}
if (g.editConfig.next !== document.activeElement){
//not currently focused
if(g.editConfig.next === g.editConfig.activeElement) {
g.editConfig.next = null;
}else{
//select contents on focus
g.editConfig.next.classList.add('selectRequired');
g.editConfig.last = g.editConfig.activeElement;
g.editConfig.activeElement = el;
g.editConfig.next = null;
}
el.focus();
}else{
//already focused to just set the flags and ignore
if(g.editConfig.next === g.editConfig.activeElement) {
g.editConfig.next = null;
}else{
g.editConfig.last = g.editConfig.activeElement;
g.editConfig.activeElement = g.editConfig.next;
g.editConfig.next = null;
}
}
}
//endregion
//region CellClick
var rxDepth = /depth(\d)/;
/**
* Check whether click was on the disclosure button
* @param {Event} e
*/
function isDisclosureClick(e){
//check for row depth
var row = e.target.closest('grid-row');
var result = rxDepth.exec(row.className);
var depth = result ? +result[1] : 0;
var ci = 10+(((+depth)+1)*16);
//find x position of click relative to the cell and compare to the cell indent
return ((e.e ? e.e.offsetX : e.offsetX) < ci);
}
/**
* Cell click event handler
* Could be in Header/Footer/Body
* @param {Event} e
* @param {View} view
* @param {HTMLElement} grid
* @param {HTMLElement} section
* @param {HTMLElement} row
* @returns {boolean}
* @memberOf DataGridController#
*/
function clickHandler(e, view, grid, section, row){
// if the grid only has a few rows it is possible to click into 'space'
// in which case row will be null
if (section) {
if (row && (row.classList.contains('main') || row.classList.contains('gantt'))) {
var rowSection = e.target.closest('grid-row-header, grid-cells');
if (rowSection) {
return itemClickHandler(e, grid, section, row, rowSection);
} else {
//clicked into the empty space to the right of the row (ignore or treat as buffer click or indicator click or ....)
}
}else{
//clicked into the empty space to the right of the row (ignore or treat as buffer click or indicator click or ....)
}
} else {
//clicked into empty space below the grid footer - do nothing
}
}
/**
* Internal cell virtual click event handler
* Could be in Header/Footer/Body
* Could be 'click' or 'key' event
* Checkbox header render behavior cbx - label (trunc label if poss) - disable or hide on row if not selectable
* @param {Event} e
* @param {HTMLElement} grid
* @param {HTMLElement} section
* @param {HTMLElement} row
* @param {HTMLElement} rowSection
* @returns {boolean}
*/
function itemClickHandler(e, grid, section, row, rowSection){
/**@type GridConfig*/
var g = Widget.getContext(grid);
//return true to signify we 'consumed' the event - stop it propagating and block any system default action
var block = true;
var sectionName = section.tagName;
var mode = g.data.mode;//tree or group or flat
if(row.classList.contains('gantt')){
if(sectionName==='GRID-SECTION'){
//deselect the previous selected row
var rowLast = row.parentElement.querySelector('grid-row.selected');
if (rowLast) rowLast.classList.remove('selected');
//select the current row
row.classList.add('selected');
var key = row.dataset.key;
var data = g.data.findByKey(key);
g.data.selectedItem = data;
var detail = {
action: 'itemClick',
item: cell,
rowIndex: g.data.selectedIndex,
colIndex: colIndex,
key: key,
data: data,
col: col
};
//if defaultPrevented don't fire the click event means someone is handling the click action....
if (!e.defaultPrevented) Views.fireActionEvent(grid, 'itemClick', false, false, detail);
}
}else{
switch (sectionName) {
case 'GRID-HEADER':
if(rowSection.tagName==='GRID-ROW-HEADER') {
// special 'indicator'/'checkbox' column
// toggle-delete-select
// it only shows if either the row is editable or selectable or deletable
if (e.target === rowSection.lastElementChild) {
//locked - displayed but no change allowed
if (!g.selectable.locked) {
// if the col is selectable then it has a checkbox so toggle
g.data.toggleField(undefined, undefined, '', g.selectable.condition);
}
}
}else {
var cell = e.target.closest('grid-cell');
if (cell) {
var colIndex = +cell.dataset.idx;
//convert to 'cell click' type event
var col = g.layout.cols[colIndex];
//its either a click on a checkbox header or a sort command or a header click(????)
if (cell.className.contains('ch-')) {
//skip anything in the column heading - head with class of ch-
} else if (col.format === 'checkbox') {
//non indicator checkbox column...
// checkbox columns are only supported where there is a single checkbox
// this is in a span that is a child of the col - headers have to allow for a label
// check uncheck all - if a row is not selectable the checkbox should be hidden or disabled.
g.data.toggleField(undefined, undefined, col.field, g.data.options.tagField === col.field ? g.selectable.condition : undefined, true);
} else {
if (g.sortable && col.sortable) {
var multiColumnSort = Key.ctrlKey;//g.layout.multiColumnSort && Key.ctrlKey;
if (multiColumnSort) {
//if multicolumn support and control key down then the header row doesnt need resetting
multiColumnSort = true;
} else {
multiColumnSort = false;
//if no multicolumn support then the header row needs resetting
var cols = g.layout.cols;
for (var i = 0; i < cols.length; i++) if (cols[i] !== col) cols[i].sort = 0;
}
// per gg now reverse the display icons as well as reverse order back to how it was originally
//now set the new col sort flag
col.sort = (col.sort + 1) % 3;
var opt = '';
switch (col.sort) {
case 0:
break;
case 1:
//'sort-up';
opt = ':a' + col.sortType + 'i';
break;
case 2:
//'sort-down';
opt = ':d' + col.sortType + 'i';
break;
}
g.data.sortOn(col.sortField + opt, multiColumnSort);
}
if (grid.dataset.fireHeaderClick) {
Views.fireActionEvent(grid, 'grdHeaderClick', false, false, {e: e,grid: grid,col: col,cell: cell});
}
}
}
}
break;
case 'GRID-FOOTER':
//do nothing - the button will call addNewRow
break;
case 'GRID-SECTION':
//clicked inside a row in the main rows container
if(rowSection.tagName==='GRID-ROW-HEADER'){
// special 'indicator'/'checkbox' column
// it only shows if the row is editable/deletable/selectable
if(e.target.tagName === 'GRID-INDICATOR') {
if (e.target === rowSection.firstElementChild) {
toggleDetailRow(g);
} else if (e.target === rowSection.lastElementChild) {
// if the col is selectable then it has a checkbox so toggle
// but is this row selected or is it just some other row
// and what if they are editing a row and then click another selector
g.data.toggleField(undefined, row.dataset.key, g.data.options.tagField, g.selectable.condition);
} else {
var item = g.data.findByKey(row.dataset.key);
gridActionHandler(g, 'delete', item);
}
}
}else {
var cell = e.target.closest('grid-cell');
if (cell) {
//clicked into a real cell
var colIndex = +cell.dataset.idx;
//convert to 'cell click' type event
var col = g.layout.cols[colIndex];
if (row.classList.contains('summaryHeader') && (mode === 'group' || (col.treeColumn && isDisclosureClick((e.e || e))))) {
//clicked on a grp header - any column in grp mode or tree column in tree mode...
if (!isDisclosureClick((e.e||e)) && grid.hasAttribute("data-grouping-header-action")) {
Views.fireActionEvent(grid, grid.getAttributeNode("data-grouping-header-action").value, false, false, g.data.findByKey(row.dataset.key));
} else {
if (e.target.dataset.editmode !== 'none') {
toggleRow(g, row);
}
}
} else if (row.classList.contains('summaryFooter')) {
//grp footer do nothing - only exists in tree mode
} else {
//a click on non indicator cell in a normal row
if (col.format === 'checkbox') {
//special handling on 'checkbox' column - this is a non indicator checkbox...
var v = g.data.toggleField(undefined, row.dataset.key, col.field, g.data.options.tagField === col.field ? g.selectable.condition : undefined, true);
} else {
//sync to the selected index...
if (((g.editmode.contains('ext') && g.layout.editform ) || g.editmode.contains('int')) && e.target.dataset.editmode !== 'none') {
var key = row.dataset.key;
var data = g.data.findByKey(key);
showEditRow(grid, data, colIndex);
} else {
//deselect the previous selected row
var rowLast = row.parentElement.querySelector('grid-row.selected');
if (rowLast) {
rowLast.classList.remove('selected');
//if the last row is editable then close edit mode before selecting the new row.
if (rowLast.classList.contains('editing')) { hideEditRow(g); }
}
//select the current row
row.classList.add('selected');
var key = row.dataset.key;
var data = g.data.findByKey(key);
g.data.selectedItem = data;
var detail = {
action: 'itemClick',
item: cell,
rowIndex: g.data.selectedIndex,
colIndex: colIndex,
key: key,
data: data,
col: col
};
//if defaultPrevented don't fire the click event means someone is handling the click action....
if (!e.defaultPrevented) Views.fireActionEvent(grid, 'itemClick', false, false, detail);
}
}
}
}
}
break;
default:
break;
}
}
return block;
}
/**
*
* @param {string} action
* @param {Event} e
* @param {HTMLTableElement} grid
* @param {HTMLElement} target
* @returns {boolean}
*/
function columnResizeHandler(action, e, grid, target){
switch(action) {
case 'start':
var g = Widget.getContext(grid);
var cell = target.closest('grid-cell');
g.gestureInfo={cell:cell,width:cell.offsetWidth, index:cell.dataset.idx};
grid.classList.add('col-resize');
cell.classList.add('col-resize');
return true;
break;
case 'move':
var g = Widget.getContext(grid);
var gi = g.gestureInfo;
var width = Math.min(Math.max(e.p2.pageX- e.p1.pageX+gi.width,50),600);
gi.cell.style.minWidth = width +'px';
gi.cell.style.maxWidth = width +'px';
gi.cell.style.width = width +'px';
break;
case 'end':
var g = Widget.getContext(grid);
var gi = g.gestureInfo;
var cell = gi.cell;
var cellIndex = gi.index;
var col = g.layout.cols[cellIndex];
var width = Math.min(Math.max(e.p2.pageX- e.p1.pageX+gi.width,50),600);
//update the overrides
g.widths[col.id] = Math.round(width/10/5)*5;
//in non pinned area - just resize
//update the column width and adjust the overall width - impacts editor
g.refreshGridLayout('resize');
cell.style.minWidth='';
cell.style.maxWidth='';
cell.style.width='';
cell.classList.remove('col-resize');
grid.classList.remove('col-resize');
g.gestureInfo=null;
Views.fireActionEvent(grid, 'layoutChange', false, false, {widths:JSON.stringify(g.widths)});
//there has been no data change so it wont really update, the change was css only but it might change row widths....
break;
}
}
//endregion
//region Editor
/**
* Fire event to check whether editing is allowed and to allow dev to reconfig the editor
* when the editmode is ext then we start in detail mode and dont toggle
* if editmode is int we start in basic mode but can toggle
* mobile layout always edit externally in a panel
* note editform is a panel in mobile but a form in desktop
* called from gridActionHandler
* @param {GridConfig} g
* @param {string} editField the name of a field
* @param {Object} [item] the row item to be edited
*/
function beginEdit(g, editField, item){
if(!item) {
//no data so create and select the new row to be edited
item = g.data.addItem();
}
if ((item.rowstate && item.rowstate.includes('D'))){
//cant edit deleted rows.....
}else {
//if grouped or HDD ensure make sure the parentItem is open...
if (g.data.options.mode !== 'flat') {
var parents = DataManager.getParents(g.data, item);
for (var i = 1; i < parents.length; i++) {
delete g.nodes[parents[i][g.data.options.key]];
}
g.allClosed = false;
}
//now can start editing
//select the data row
if(g.data.selectedItem !== item)g.data.selectedItem = item;
var view = Views.findView(g.el);
//notify edit is beginning
// - default to basic mode - dev can flip it if he likes
// - if 'ext' then we start with detail displayed and cant flip back
// - if 'ext-int' then we start with detail displayed and CAN flip back
// - if can toggle we need to read the state - either from edit config or persistant cache....
// note that gg decided that if no mobile format exists then the desktop format is to be used on mobile
// which can lead to some conflicts with editing...
// ext int ext-int|ins|del
var aid = view.view.name;
var gridState = cache[aid];
var editorState = (gridState && gridState[g.el.id]) || '';
var editmode = g.editmode.contains('ext-int') ? 'ext' : g.editmode.contains('int') ? 'int' : g.editmode.contains('ext') ? 'ext' : '';
var editform = g.layout.editform;
var editToggle = !!(editmode === 'int' && editform);
//make sure if the toggle state was persisted it is still valid
var editor = (editToggle && editorState) || (editmode === 'ext' ? 'detail' : 'basic');
var editKey = item[g.data.options.key];
//create the initial editor config
g.editConfig.next = g.editConfig.last = g.editConfig.activeElement = null;
g.editConfig.gridRowsEditOffset = 0;
g.editConfig.active=false;
g.editConfig.action = 'itemEditBegin';
g.editConfig.data = item;
g.editConfig.required = '';
g.editConfig.editable = '';
g.editConfig.editor = editor;
g.editConfig.visible = '';
g.editConfig.editmode = editmode;
g.editConfig.editform = editform;
g.editConfig.editField = editField || '';
g.editConfig.toggle = editToggle;
g.editConfig.editkey = editKey;
g.editConfig.selectable = g.selectable.state && !g.selectable.locked && g.selectable.condition(item);
//fire the item edit event to check for permissions and editor config
var editing = Views.fireActionEvent(g.el, 'itemEdit', false, true, g.editConfig);
if (Views.displayMode === 'mobile') {
// render mobile version - if set int or ext assume they meant ext and if there is a form we are good
if (editing && ((editmode==='int' || editmode==='ext') && editform)) {
//notice we dont set the editing flag, dont focus a field etc..... the grid doesn't know we are actually editing
//really its just a panel
Views.openPanel(view, g.editConfig.editform);
//but cfg the controls...
//Forms.setDisabled(view.el,true,g.layout.readonly);
} else {
//cant edit this row
}
} else {
// render desktop version - int or ext but only if there is also a form
if (editing && (editmode==='int' || (editmode==='ext' && editform))) {
//turn datasource lock off
g.data.options.editLock = false;
//set the grid level editing flag
g.data.options.editing = g.editConfig.editing = editing;
//set grid into edit mode
g.el.classList.add('editing');
// we render the whole grid and if editing is true when we get to the selectedItem we render the editor
g.editConfig.nextAction = 'edit-render';
g.editConfig.nextActionKey = g.editConfig.editkey;
//if we added a row we need to refresh the ds and wait for render
//if the ds is waiting a refresh we need to wait for render
//if no refresh pending or added row we still need to trigger a render somehow..
g.data.refresh();
} else {
//cant edit this row
}
}
}
}
/**
* close:true, close:true, cancel:false, delete:true, show:false, insert:false
* event driven so on the JSMicroTask loop
* @param {(GridConfig|HTMLTableElement)} g
* @param {string} action
* @param {Object} [item]
* @param {string} [field]
* @returns {boolean}
*/
function gridActionHandler(g, action, item, field){
var result=true;
if(g instanceof HTMLElement) g = Widget.getContext(g);
if(g.editConfig.editing){
// we are actively editing a row - ext or int mode?
// we only get here in non mobile mode... mobile uses an independent form
var row = g.el.querySelector('grid-row.editing');
// an action arrives before the editor has been displayed or after its been removed
// probably a close before insert or a straight close triggering on a scroll?
if(!row)return;
var form = row.querySelector('FORM');
if(!form)return;
if(!form.ctx.inactive)Forms.commitActiveElement(form);
switch(action){
case 'delete':
//from the int editor row delete button - remove editor no save or validation
//check whether we need to ask for delete
var msg = g.prompts['DELETE'];
if(msg){
Notify.confirm(msg).always(function (result) {
if (result === false) {//no - just refocus the form
//TODO:create std focus this form method
Focus.focusLater(Focus.findFirstFocusable(form));
}else{
//we just destroy it - unbinds but doesn't save.... deleting a record triggers a data save...
//unrender
renderEditor(g, row, 'edit-close');
//auto refreshes
g.data.itemUpdated('D', item, true);
}
});
}else {
//we just destroy it - unbinds but doesn't save.... deleting a record triggers a data save...
//unrender
renderEditor(g, row, 'edit-close');
//auto refreshes
g.data.itemUpdated('D', item, true);
}
break;
case 'toggle-detail':
// toggling between inline and detail (int-basic/int-detail)
// make sure that we actually support inline and not its ext
// ext shows detail immediately and doesnt toggle
// we dont really want to trigger anything just switch forms
if(g.editConfig.editmode==='int' && g.editConfig.editform) {
//there is no grid refresh we just update the editor layout
renderEditor(g, row, 'edit-toggle');
}
break;
case 'close-editor':
//is the form already dead
if(form.ctx.inactive) {
// already closed - in forms - just tail end here
// called from global dismiss or autoclose behavior
if(form.ctx.mode==='edit-next'){
//unrender
renderEditor(g, row, 'edit-close');
editRow(g.el, 'next', g.editConfig.editField);
}else{
//unrender
renderEditor(g, row, 'edit-close');
// need to force a refresh of the grid UI
g.data.refresh();
}
}else {
// no, so then first close it
// called by a direct explicit call from dev
// save it - this should lead back here to above
Forms.setEditMode(form, 'endEdit');
}
break;
case 'insert':
//add a blank row - flag as insert row - launch and focus editor
if(g.editConfig.data.rowstate === 'IN'){
//current edit row is not dirty so don't insert just continue
form.elements[0].focus();
}else{
// - already editing
// - add a blank row - flag as insert row - launch and focus editor
// - need to update for last row + the add item before we render the grid
// save, close and destroy the current editor
// this will trigger close-editor and will turn editing off
Forms.setEditMode(form, 'endEdit');
//now edit it
beginEdit(g, g.layout.cols[1].editField || g.layout.cols[1].field);
}
break;
case 'open-editor':
//clicked on a another row or edit next btn - need to save old row and reset the editor
//or tabbed out of last form element - need to close old row editor and move to next row or insert a new row and open the new roweditor
//save current row if dirty then move to new row
//Need to fire this event so that screens with grid auto-save like the schedule can keep track of which row was just clicked on before the auto-save was triggered
Views.fireActionEvent(g.el, 'open-editor', false, false, {item: item, field: field});
Forms.save(form, '', '');
//totals and groups maybe invalid if dirty so we force the ds to refresh before showing the new row....
beginEdit(g, field, item);
break;
case 'cancel':
//unrender
renderEditor(g, row, 'close');
break;
default:
break;
}
}else{
///var row = item instanceof HTMLElement ? item : null;
switch(action) {
case 'delete':
//from the int editor row delete button - remove editor no save or validation
//check whether we need to ask for delete
if(item) {
//find the row and flag it.....
var key = item[g.data.options.key];
var row = g.el.querySelector('grid-row[data-key="'+key+'"]:not(.hideChild)');
if(row)row.classList.add('prompt');
var msg = g.prompts['DELETE'];
if (msg) {
Notify.confirm(msg).always(function (result) {
if (result === false) {
//ntg to do
if(row)row.classList.remove('prompt');
} else {
//auto refreshes
g.data.itemUpdated('D', item, true);
}
});
} else {
//auto refreshes
g.data.itemUpdated('D', item, true);
}
}
break;
case 'close-editor':
case 'cancel':
//cant happen or ntg to do
break;
case 'insert':
//add a blank row - flag as insert row - launch and focus editor
//select the row to be edited - make sure parents are open...
beginEdit(g, field || g.layout.cols[0].editField || g.layout.cols[0].field);
break;
case 'open-editor'://show the editor for this row
// its a legit target
beginEdit(g, field, item);
break;
}
}
return result;
}
/**
* Show the row editor
* @param {HTMLTableElement} grid
* @param {Object} row row data object
* @param {string|number} [field] field name or keyword (first) or col index
* @param {boolean} reset
*/
function showEditRow(grid, row, field, reset){
var g = grid instanceof GridConfig ? grid : Widget.getContext(grid);
//name of field to edit
if(row || g.editmode.contains('ins')) {
field = field==='first' ? 0 : field || 0;
var col;
if(isNaN(field)){
col = getColumnFromField(g.layout.cols, field);
}else{
col = g.layout.cols[Math.max(0, Math.min(g.layout.cols.length - 1, field))];
}
col = getEditableColumn(g.layout.cols, col.i);
//dev creates editRow with no editable fields so we cant block show editor just becuase there is nothing to edit
//if(col.editField)
gridActionHandler(g, 'open-editor', row, col.editField);
}else{
hideEditRow(grid);
}
}
/**
* find a column that is editable
* @param {Array} cols
* @param {number} index
* @returns {Object}
*/
function getEditableColumn(cols, index){
//the column to start looking in....
var start = Math.max(0, Math.min(index, cols.length-1));
for (var i = 0, len = cols.length; i < len; i++) {
if(cols[(i + start) % len].editField){
return cols[(i + start) % len];
}
}
//if we get here we couldnt find an editable field... so just return the clicked column
return cols[start];
}
/**
* Find the target column from the field name
* @param cols
* @param field
* @returns {*}
*/
function getColumnFromField(cols, field){
for (var i = 0, len = cols.length; i < len; i++) {
var col = cols[i];
if(cols[i].field === field)return cols[i];
}
return cols[0];
}
/**
* Hide the row editor
* called on a form delete or explicitly by dev
* @param {HTMLTableElement} grid
* @memberOf DataGridController#
*/
function hideEditRow(grid){
var g = grid instanceof GridConfig ? grid : Widget.getContext(grid);
gridActionHandler(g, 'close-editor');
}
/**
*
* @param {GridConfig} grid
*/
function toggleDetailRow(grid){
/*@type GridConfig*/
var g = grid instanceof GridConfig ? grid : Widget.getContext(grid);
//toggle between std and custom editor.....
gridActionHandler(g, 'toggle-detail');
}
/**
*
* @param {HTMLTableElement|GridConfig} grid
* @memberOf DataGridController#
*/
function addNewRow(grid){
/*@type GridConfig*/
var g = grid instanceof GridConfig ? grid : Widget.getContext(grid);
gridActionHandler(g,'insert');
}
//endregion
//region Hierarchical Grid
/**
* Toggle a row disclosure in a hierarchical grid
* close the editor if open
* select a new row
* open/close the node
* @param {GridConfig} g
* @param {HTMLElement} row
* @memberOf DataGridController#
*/
function toggleRow(g, row){
//close the editor
gridActionHandler(g, 'close-editor');
//toggle the node state so when it gets refresh it will reflect this state
g.nodes[row.dataset.key] = !(row.classList.contains('collapsed'));
//refresh grid
g.data.refresh();
}
/**
* Expand all rows in a disclosure
* @param {HTMLTableElement} el
* @memberOf DataGridController#
*/
function expandAll(el){
var g = Widget.getContext(el);
gridActionHandler(g, 'close-editor');
g.allClosed=false;
g.nodes = {}; //empty list of closed nodes
//refresh grid
g.data.refresh() ;
}
/**
* Collapse all rows
* @param {HTMLTableElement} el
* @memberOf DataGridController#
*/
function collapseAll(el){
var g = Widget.getContext(el);
gridActionHandler(g, 'close-editor');
g.allClosed=true;
g.nodes = {}; //empty list of closed nodes to match expand all
//refresh grid
g.data.refresh();
}
//endregion
self.updateGridLayout = updateGridLayout;
self.updateGridElement = updateGridElement;
self.renderGridHeader = renderGridHeader;
self.addNewRow = addNewRow;
self.hideEditRow = hideEditRow;
self.editRow = editRow;
self.scrollRow = scrollRow;
self.expandAll = expandAll;
self.collapseAll = collapseAll;
self.clickHandler = clickHandler;
return self;
};
//shortcut
/**@type DataGridController*/
window.DataGrid = new DataGridController();
//endregion
/**
* The default options for the actual grid table object
*/
Widget.setDefaultOptions('grid',{
//the id for the layout template
tmpl:'',
//named list source
source:''
});
})();