Ext JS EditorGridPanel - How to split cell to show multiple rows - extjs

In Ext.grid.EditorGridPanel table how to split a cell to have two or three rows?
Like the below image

I managed to do a kind of rowspan instead of row splitting. IMO it's easier and it looks the same as grid on attached image. Example code:
var grid = new Ext.grid.EditorGridPanel({
[...],
// hook up events
initComponent: function () {
Ext.grid.GridPanel.prototype.initComponent.call(this);
this.getView().on('refresh', this.updateRowSpan, this);
this.getView().on('rowupdated', this.updateRowSpan, this);
},
onLayout : function(vw, vh) {
this.updateRowSpan();
},
// set span on rows
updateRowSpan: function() {
var columns = this.getColumnModel().config,
view = this.getView(),
store = this.getStore(),
rowCount = store.getCount(),
column = columns[0], // put propert column index here
dataIndex = column.dataIndex,
spanCell = null,
spanCount = null;
spanValue = null;
for (var row = 0; row < rowCount; ++row) {
var cell = view.getCell(row, 0),
record = store.getAt(row),
value = record.get(dataIndex);
if (spanValue != value) {
if (spanCell !== null) {
this.setSpan(Ext.get(spanCell), spanCount);
}
spanCell = cell;
spanCount = 1;
spanValue = value;
} else {
spanCount++;
}
}
if (spanCell !== null) {
this.setSpan(Ext.get(spanCell), spanCount);
}
},
// set actual span on row
setSpan: function(cell, count) {
var view = this.getView(),
innerCell = Ext.get(cell.down('*')),
height = cell.getHeight(),
width = cell.getWidth();
cell.setStyle('position', 'relative');
if (count == 1) {
innerCell.setStyle('position', '');
innerCell.setStyle('height', '');
innerCell.setStyle('height', '');
} else {
innerCell.setStyle('position', 'absolute');
innerCell.setStyle('height', (height * count - cell.getPadding('tb') - innerCell.getPadding('tb')) + 'px');
innerCell.setStyle('width', (width - cell.getPadding('lr') - innerCell.getPadding('lr')) + 'px');
}
}
});
This code changes style of .x-grid3-cell-inner by applying position: absolute and big enough size to cover rows below. Notice that you must also apply some opaque background to make it work. Working sample: http://jsfiddle.net/RpxZ5/8/
I first wrote code for Ext JS 4, if you interested, here is working sample: http://jsfiddle.net/wQSQM/3/

Related

Isotope: Combined multiple checkbox and searchbox filtering

I'm trying to combine the Isotope multiple checkbox filtering with a searchbox.
I used the example with the checkbox filters from here and tried to implement the searchbox but with no luck.
Just the checkbox filtering works well. I think i'm close to the solution but my javascript skills are at a very beginner level.
I commented out the section of what i've tried to implement.
Thank you for some hints
// quick search regex
var qsRegex;
var $grid;
var filters = {};
var $grid = $('.grid');
//set initial options
$grid.isotope({
layoutMode: 'fitRows'
});
$(function() {
$grid = $('#grid');
$grid.isotope();
// do stuff when checkbox change
$('#options').on('change', function(jQEvent) {
var $checkbox = $(jQEvent.target);
manageCheckbox($checkbox);
var comboFilter = getComboFilter(filters);
/*var searchResult = qsRegex ? $(this).text().match(qsRegex) : true;
var filterResult = function() {
return comboFilter && searchResult;
}*/
$grid.isotope({
filter: comboFilter //or filterResult
});
});
});
function getComboFilter(filters) {
var i = 0;
var comboFilters = [];
var message = [];
for (var prop in filters) {
message.push(filters[prop].join(' '));
var filterGroup = filters[prop];
// skip to next filter group if it doesn't have any values
if (!filterGroup.length) {
continue;
}
if (i === 0) {
// copy to new array
comboFilters = filterGroup.slice(0);
} else {
var filterSelectors = [];
// copy to fresh array
var groupCombo = comboFilters.slice(0); // [ A, B ]
// merge filter Groups
for (var k = 0, len3 = filterGroup.length; k < len3; k++) {
for (var j = 0, len2 = groupCombo.length; j < len2; j++) {
filterSelectors.push(groupCombo[j] + filterGroup[k]); // [ 1, 2 ]
}
}
// apply filter selectors to combo filters for next group
comboFilters = filterSelectors;
}
i++;
}
var comboFilter = comboFilters.join(', ');
return comboFilter;
}
// use value of search field to filter
var $quicksearch = $('.quicksearch').keyup(debounce(function() {
qsRegex = new RegExp($quicksearch.val(), 'gi');
$grid.isotope();
}, ));
// debounce so filtering doesn't happen every millisecond
function debounce(fn, threshold) {
var timeout;
threshold = threshold || 100;
return function debounced() {
clearTimeout(timeout);
var args = arguments;
var _this = this;
function delayed() {
fn.apply(_this, args);
}
timeout = setTimeout(delayed, threshold);
}
}
function manageCheckbox($checkbox) {
var checkbox = $checkbox[0];
var group = $checkbox.parents('.option-set').attr('data-group');
// create array for filter group, if not there yet
var filterGroup = filters[group];
if (!filterGroup) {
filterGroup = filters[group] = [];
}
var isAll = $checkbox.hasClass('all');
// reset filter group if the all box was checked
if (isAll) {
delete filters[group];
if (!checkbox.checked) {
checkbox.checked = 'checked';
}
}
// index of
var index = $.inArray(checkbox.value, filterGroup);
if (checkbox.checked) {
var selector = isAll ? 'input' : 'input.all';
$checkbox.siblings(selector).prop('checked', false);
if (!isAll && index === -1) {
// add filter to group
filters[group].push(checkbox.value);
}
} else if (!isAll) {
// remove filter from group
filters[group].splice(index, 1);
// if unchecked the last box, check the all
if (!$checkbox.siblings('[checked]').length) {
$checkbox.parents('.option-set').find(selector).prop('checked', false);
}
}
I found the solution by myself, but i had to add a second function for returning the searchresult. Otherwise the search function is triggered only after using a checkbox or leaving the search box input field.
How could i avoid this redundand code?
JS:
// use value of search field to filter
var $quicksearch = $('.quicksearch').keyup(debounce(function() {
qsRegex = new RegExp($quicksearch.val(), 'gi');
$grid.isotope();
}, 200));
$(function() {
$grid = $('#grid');
$grid.isotope({
filter: function() {
var searchResult = qsRegex ? $(this).text().match(qsRegex) : true;
return searchResult;
}
});
// do stuff when checkbox change
$('#options').on('change', function(jQEvent) {
var $checkbox = $(jQEvent.target);
manageCheckbox($checkbox);
var comboFilter = getComboFilter(filters);
$grid.isotope({
filter: function() {
var buttonResult = comboFilter ? $(this).is(comboFilter) : true;
var searchResult = qsRegex ? $(this).text().match(qsRegex) : true;
return buttonResult && searchResult;
}
});
});
});

Extjs - drag drop restriction, containment

In Extjs, I want to know whether I can restrict the dragging of elements within a specific x,y co-ordinates, just like an option, containment in jQuery-UI.
Currently this is my code:
abc.prototype.initDrag = function(v) {
v.dragZone = new Ext.dd.DragZone(v.getEl(), {
containerScroll : false,
getDragData : function(e) {
var sourceEl = e.getTarget(v.itemSelector, 10);
var t = e.getTarget();
var rowIndex = abc.grid.getView().findRowIndex(t);
var columnIndex = abc.grid.getView().findCellIndex(t);
var abcDragZone = v.dragZone ; //Ext.getCmp('idabcDragZone');
var widthToScrollV = $(window).width()-((columnIndex-1)*100);
var widthToScrollH = $(window).height()-((5-rowIndex)*30);
abcDragZone.setXConstraint(0,widthToScrollV);
abcDragZone.setYConstraint(widthToScrollH,0);
if ((rowIndex !== false) && (columnIndex !== false)) {
if (sourceEl) {
abc.isDragged = true;
def.scriptGrid.isDraggableForObject = false;
def.scriptGrid.dragRowIndex = false;
d = sourceEl.cloneNode(true);
d.id = Ext.id();
d.textContent = "\$" + abc.grid.getColumnModel().getColumnHeader(columnIndex);
return {
ddel : d,
sourceEl : d,
sourceStore : v.store
}
}
}
},
getRepairXY : function() {
return this.dragData.repairXY;
},
});
}
But the problem is that the initdrag is called when the csv sheet is added to DOM. Only when its added that element can be accessed and the individual cells' drag limits can be set. So once I add a csv, the limits are not getting set. If I add it again to DOM then the limits work. Is there an option like the jQuery UI containment for draggable, here in extjs?
edit:
I even tried :
constrainTo( constrainTo, [pad], [inContent] )
body had an id of #abc
when I tried with
dragZoneObj.startDrag = function(){
this.constrainTo('abc');
};
which is a method of the DragZone class. It still did not cover the whole body tag.

Delete multiple items from grid using CheckboxSelectionModel

Using ExtJs4.1 on Sencha Architect.
I have following code in my onDeleteButton code
onDeleteButtonClick: function(button, e, options) {
var active = this.activeRecord;
var myGrid = Ext.getCmp('publisherResultsGridView'),
sm = myGrid.getSelectionModel(),
selection = sm.getSelection(); // gives you a array of records(models)
if (selection.length > 0){
for( var i = 0; i < selection.length; i++) {
this.application.log('OnDeleteItemID is ' + selection);
}
this.remove(selection);
}
Code for Remove Function
remove: function(record) {
var store = Ext.getStore('PublisherProperties');
store.proxy.url = MasterDataManager.globals.url + "Publishers/";
store.remove(record);
store.sync();
When I run it, I could see an array of objects in my log, also I dont get any errors after the remove function is executed. But the store doesnt update, I mean it doesnt remove the selected items.
Can somebody please help me.
Thanks
I solved my problem by making the following changes.
To onDeleteButtonClick
if (selection.length > 0){
for( var i = 0; i < selection.length; i++) {
this.application.log('OnDeleteItemID is ' + selection[i].data.id);
this.remove(selection[i]);
}
}
To Remove function
remove: function(record) {
var store = Ext.getStore('PublisherProperties');
this.application.log('Remove Function is ' + record);
store.proxy.url = MasterDataManager.globals.url + "Publishers/" + record.data.id;
store.load({
scope : this,
callback : function(records, operation, success){
if (records.length > 0){
var store2 = Ext.getStore('PublisherProperties');
store2.proxy.url = MasterDataManager.globals.url + "Publishers/";
store2.remove(records[0]);
store2.sync();
}
}
});
//store.remove(record);
//store.sync();

How can I make a grid with checkboxes that maintain state between page loads?

How can we make checkboxes remain checked when the page is refreshed in a Sencha ExtJS 3.3.0 GridPanel?
I have a GridPanel which displays some information with checkboxes. When the page is refreshed, the checkbox should still be checked.
Any suggestions, ideas, or code samples?
Had the same problem and I fixed it in such way - manually save id's of records that I show in cookies. Solution is not beautiful, but works for me.
store.on({
'beforeload': function () {
var checkeditems = [];
for(var i=0;i<gridResources.selModel.selected.length;i++)
{ checkeditems.push(grid.selModel.selected.items[i].data.ID);
}
if(checkeditems.length>0)
setCookie("RDCHECKBOXES", checkeditems.join("|"));
},
'load': function () {
if (getCookie("RDCHECKBOXES")) {
var checkeditems = getCookie("RDCHECKBOXES").split("|");
for (var i = 0; i<gridResources.store.data.items.length && checkeditems.length>0; i++) {
for(var j=0;j<checkeditems.length;j++) {
if (gridResources.store.data.items[i].data.ID == checkeditems[j]) {
gridResources.selModel.select(gridResources.store.data.items[i], true);
checkeditems.splice(j, 1);
break;
}
}
}
}
}
});
Here are code for functions getCookie() and setCookie():
// Example:
// setCookie("foo", "bar", "Mon, 01-Jan-2001 00:00:00 GMT", "/");
function setCookie (name, value, expires, path, domain, secure) {
document.cookie = name + "=" + escape(value) +
((expires) ? "; expires=" + expires : "") +
((path) ? "; path=" + path : "") +
((domain) ? "; domain=" + domain : "") +
((secure) ? "; secure" : "");
}
// Example:
// myVar = getCookie("foo");
function getCookie(name) {
var cookie = " " + document.cookie;
var search = " " + name + "=";
var setStr = null;
var offset = 0;
var end = 0;
if (cookie.length > 0) {
offset = cookie.indexOf(search);
if (offset != -1) {
offset += search.length;
end = cookie.indexOf(";", offset)
if (end == -1) {
end = cookie.length;
}
setStr = unescape(cookie.substring(offset, end));
}
}
return(setStr);
}
Have you looked at all at the ExtJS documentation or the included samples? There's a sample grid using the CheckColumn extension that does exactly what you ask.
In the example linked, take note that the checkbox column is linked to a boolean record field
// in your record
{name: 'indoor', type: 'bool'}
and represented in the grid's column model by a CheckColumn:
// in the grid's column model
xtype: 'checkcolumn',
header: 'Indoor?',
dataIndex: 'indoor',
width: 55
This way, when boolean data comes into the store from the server in JSON or XML, the values are represented as checkboxes in the grid. As long as you write your changes to the server, your checkbox boolean values will be preserved.

How to get the minimal required width for a dijit ComboBox or FilteringSelect?

I'm using ComboBox and FilteringSelect in a dialog and have yet been unable to make the controls have the minimal required width only, i.e. being just large enough to display the longest text from a drop-down list. Also the control must not be set to a fixed width since the actual content of the drop-down lists gets filled in from a translation database.
In plain html with a simple input of type text it works smooth just by default. However since even all examples at dojotoolkit.org show the very same behavior it seems to me that dojo introduces a minimum width for all those input controls. Thus I wonder if it can be done at all...
Thanks in advance!
I had the same problem; after some struggle, I decided to adapt this to my problem.
In my case, I was forced to use an old version of dojo, and the FilteringSelect were declarative, so I had to use a hack (the last three lines of the code below) to be sure my function would be executed at the right time.
So, the function below takes all dijit widgets, looks for those stored element is a select (getAllDropdowns), and for each it takes its options, copies the content in a new element moved outside of the visible screen and takes the width of that element, adjusted with padding (this may not be your case, so check getWidth); then it takes the max of those widths and compare it with the current length of the input element, and if the longest option is bigger, adjust the input and the outmost div widths.
This answer comes quite late, but since it was not easy for me to come to this solution, I thought it may be worth sharing.
// change dropdowns width to fit the largest option
function fixDropdownWidth() {
var getAllDropdowns = function() {
var dropdowns = [];
dijit.registry.forEach(function(widget, idx, hash) {
if (widget.store) {
var root = widget.store.root;
if (root && root.nodeName.toLowerCase() == 'select') {
dropdowns.push(widget);
}
}
});
return dropdowns;
};
var getTesterElement = function() {
var ret = dojo.query('tester');
if (ret.length) {
return ret;
}
else {
document.body.appendChild(document.createElement('tester'));
return dojo.query('tester');
}
};
var getWidth = function(el) {
var style = dojo.getComputedStyle(el);
return el.clientWidth + parseInt(style.paddingLeft) + parseInt(style.paddingRight);
};
var getOptionWidth = function(option) {
var testEl = getTesterElement();
testEl[0].innerHTML = option.innerHTML;
return getWidth(testEl[0]);
};
var dropdowns = getAllDropdowns();
var testEl = getTesterElement();
dojo.style(testEl[0], {
position: 'absolute',
top: -9999,
left: -9999,
width: 'auto',
whiteSpace: 'nowrap'
});
for (var i = 0; i < dropdowns.length; i++) {
var input = dropdowns[i].textbox;
dojo.style(testEl[0], {
fontSize: dojo.style(input, 'fontSize'),
fontFamily: dojo.style(input, 'fontFamily'),
fontWeight: dojo.style(input, 'fontWeight'),
letterSpacing: dojo.style(input, 'letterSpacing')
});
var max = 0;
var treshold = 5;
dojo.query('option', dropdowns[i].store.root).forEach(function(el, idx, list) {
max = Math.max(max, getOptionWidth(el) + treshold);
});
if (max > getWidth(dropdowns[i].textbox)) {
var icon = dojo.query('.dijitValidationIcon', dropdowns[i].domNode)[0];
dojo.style(dropdowns[i].textbox, {width: max + 'px'});
var width = max + getWidth(icon) + getWidth(dropdowns[i].downArrowNode) + treshold;
dojo.style(dropdowns[i].domNode, {
width: width + 'px'
});
}
}
}
dojo.addOnLoad(function() {
dojo._loaders.push(fixDropdownWidth);
});
var dropDowns = [];
var getAllDropdowns = function (dropDowns) {
array.forEach(dijit.registry.toArray(), function (widget) {
if (widget.store) {
if (widget.domNode.classList.contains("dijitComboBox")) {
dropDowns.push(widget);
}
}
});
};
getAllDropdowns(dropDowns);
var maxLength = 0;
array.forEach(dropDowns, function (dropDown) {
var opts = dropDown.get("store").data;
array.forEach(opts, function (option) {
var optionValue = option[dropDown.get("searchAttr")];
var dropDownCurrentStyle = window.getComputedStyle(dropDown.domNode);
var currentOptionWidth = getTextWidth(optionValue, dropDownCurrentStyle.fontStyle, dropDownCurrentStyle.fontVariant, dropDownCurrentStyle.fontWeight, dropDownCurrentStyle.fontSize, dropDownCurrentStyle.fontFamily);
if (currentOptionWidth > maxLength) {
maxLength = currentOptionWidth;
}
});
dropDown.domNode.style.width = maxLength + "px";
maxLength = 0;
});
function getTextWidth(text, fontStyle, fontVariant, fontWeight, fontSize, fontFamily) {
// re-use canvas object for better performance
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
var font = fontStyle + " " + fontVariant + " " + fontWeight + " " + fontSize + " " + fontFamily;
context.font = font;
canvas.innerText = text;
var metrics = context.measureText(text);
return metrics.width + 25; //change this to what you need it to be
}

Resources