Is there a plugin for the ExtJS Grid that automatically hides "empty" columns?
A column should be deemed "empty" if the value of the mapped field for all Records in the underlying store matches a given "emptiness" criteria (a given value or, better, a function).
Run-time add/remove/update operations on the underlying store should hide/un-hide columns accordingly.
Thanks!
I had to do something similar to this... here is a "Hiding Column Model" that will hide/show columns based on the return value of the "fieldHasData" method... it is probably a close start to what you were asking
Ext.ux.grid.HidingColumnModel = function() {
var Class = Ext.extend(Ext.grid.ColumnModel, {
constructor:function(config) {
Class.superclass.constructor.call(this, config);
},
onGridStoreLoad:function(store, records, options) {
store.fields.each(function(item, index, length) {
var colIndex = this.findColumnIndex(item.name);
if (colIndex >= 0) {
this.setHidden(colIndex, !this.fieldHasData(item.name, records));
}
}, this);
},
fieldHasData:function(field, records) {
var hasData = false;
Ext.each(Ext.pluck(records, "data"), function(item, index, allItems) {
if (item[field]) {
hasData = true;
}
});
return hasData;
}
});
return Class;
}();
And then in your grid... do add the listener on the column model
var columnModel = new Ext.ux.grid.HidingColumnModel(),
store = ... {create your store},
gridPanel = new Ext.grid.GridPanel({
...
store:store,
columnModel:columnModel,
...
});
store.on('load', columnModel.onGridStoreLoad, columnModel);
Since I could not find a similar plugin anywhere, I just implemented it myself :)
The code is on the extjs/sencha forums here:
http://www.sencha.com/forum/showthread.php?140685-Grid-Plugin-dynamically-hiding-columns-matching-quot-emptiness-quot-criteria&p=626670#post626670
Related
In protractor, I want to verify whether added value is displaying in the grid.
How do I validate it?
The grid looks like this:
Try it at your end and let me know it works:
var a = element(by.xpath("//div[contains(text(), 'test_protractor')]")).getText().then(function(msg){
console.log(msg)
expect(msg).toEqual("test_protractor")
})
If there are multiple and you want to get only first row then
var a = element.all(by.xpath("//div[contains(text(), 'test_protractor')]")).get(1).getText().then(function(msg){
console.log(msg)
expect(msg).toEqual("test_protractor")
})
Let me assume you have created a new row with below details,
var newRow = {
"obligation_name" : "sudharsan",
"status" : "",
"module" : "Test Module"
}
If you want to verify that the newly entered row is displayed,You can try the below code,
var rowList = element.all(by.repeater("(colRenderIndex, col) in colContainer.renderedColumns"));
var expectedRow = rowList.filter(function(rowElement,index) {
var columnList = rowElement.all(by.css("div.ui-grid-cell-contents"));
return columnList.getText().then(function(columnValues) {
return (columnValues[0]== newRow["obligation_name"]) && (columnValues[1] == newRow["status"]) && (columnValues[2] == newRow["module"]);
});
});
expect(expectedRow.count()).toBeGreaterThan(0);
I am using Bootstrap Table by wenzhixin. I need to filter by several columns. For example, I need to filter Countries and then Cities.
I have two select fields for each field in the table (Select Country and Select City).
The way I am doing it right now is I apply filterBy method on change of select option:
$('#countries-list').on('change', function (evt) {
var selectedCountry = $('#countries-list').select2('val');
if (selectedCountry == "all") {
$tableFilter.bootstrapTable('filterBy');
} else {
$tableFilter.bootstrapTable('filterBy', {country: selectedCountry});
}
});
$('#cities-list').on('change', function (evt) {
var selectedCity = $('#cities-list').select2('val');
if (selectedCity == "all") {
$tableFilter.bootstrapTable('filterBy');
} else {
$tableFilter.bootstrapTable('filterBy', {city: selectedCity});
}
});
The problem is that they overwrite each other. I would appreciate any suggestions on how to apply second filter only to the results of the first one, not to the entire dataset.
Thank you!
I suggest that you use the bootstrap-table extension Filter-Control.
Example
Github project
Fairly old question but I just struggled with the same thing today and just figured out a solution.
Set needed variables:
var $table = $('#table-id')
var FilterOptions = {}
Create a function to check if a object is empty:
function isEmpty(obj) {
for(var key in obj) {
if(obj.hasOwnProperty(key))
return false;
}
return true;
}
Create a function that will filter the table with the help of our isEmpty() function:
function FilterTable(){
if(isEmpty(FilterOptions)){
$table.bootstrapTable('filterBy', {})
} else {
$table.bootstrapTable('filterBy', FilterOptions)
}
}
Then for each of our select fields we will create a function that adds/change a property to our FilterOptions object depending on what is selected or deletes the property if the default option is selected ("all" in this example). At the end we filter our table with our FilterTable() function.
$('#Selector_column1_filter').change(function(){
if ($(this).val() != "all") {
FilterOptions.column1 = $(this).val();
} else {
delete FilterOptions.column1;
}
FilterTable();
})
Scenario: I have a standard dropdown list and when the value in that dropdownlist changes I want to update another dropdownlist that exists in a tinyMCE control.
Currently it does what I want when I open the page (i.e. the first time)...
function changeParent() {
}
tinymce.create('tinymce.plugins.MoePlugin', {
createControl: function(n, cm) {
switch (n) {
case 'mylistbox':
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts',
onselect: function(v) {
tinyMCE.execCommand("mceInsertContent",false,v);
}
});
<% foreach (var insert in (ViewData["Inserts"] as List<String>)) { %> // This is .NET
yourobject = '<%= insert %>'; // This is JS AND .NET
mlb.add(yourobject, yourobject); // This is JavaScript
<% } %>
// Return the new listbox instance
return mlb;
}
return null;
}
});
<%= Html.DropDownList(Model.Record[184].ModelEntity.ModelEntityId.ToString(), ViewData["Containers"] as SelectList, new { onchange = "changeParent(); return false;" })%>
I am thinking the way to accomplish this (in the ChangeParentFunction) is to call a controller action to get a new list, then grab the 'mylistbox' object and reassign it, but am unsure how to put it all together.
As far as updating the TinyMCE listbox goes, you can try using a tinymce.ui.NativeListBox instead of the standard tinymce.ui.ListBox. You can do this by setting the last argument to cm.createListBox to tinymce.ui.NativeListBox. This way, you'll have a regular old <select> that you can update as you normally would.
The downside is that it looks like you'll need to manually hook up your own onchange listener since NativeListBox maintains its own list of items internally.
EDIT:
I played around a bit with this last night and here's what I've come up with.
First, here's how to use a native list box and wire up our own onChange handler, the TinyMCE way:
// Create a NativeListBox so we can easily modify the contents of the list.
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
As far as updating the list box at runtime, your idea of calling a controller action to get the new items is sound; I'm not familiar with ASP.NET, so I can't really help you there.
The ID of the <select> that TinyMCE creates takes the form editorId_controlId, where in your case controlId is "mylistbox". Firebug in Firefox is the easiest way to find the ID of the <select> :)
Here's the test button I added to my page to check if the above code was working:
<script type="text/javascript">
function doFoo() {
// Change "myEditor" below to the ID of your TinyMCE instance.
var insertsElem = document.getElementById("myEditor_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
optElem.value = "1";
optElem.text = "Foo";
insertsElem.add(optElem, null);
optElem = document.createElement("option");
optElem.value = "2";
optElem.text = "Bar";
insertsElem.add(optElem, null);
}
</script>
<button onclick="doFoo();">FOO</button>
Hope this helps, or at least gets you started.
Step 1 - Provide a JsonResult in your controller
public JsonResult GetInserts(int containerId)
{
//some code to get list of inserts here
List<string> somedata = doSomeStuff();
return Json(somedata);
}
Step 2 - Create javascript function to get Json results
function getInserts() {
var params = {};
params.containerId = $("#184").val();
$.getJSON("GetInserts", params, updateInserts);
};
updateInserts = function(data) {
var insertsElem = document.getElementById("183_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
for (var item in data) {
optElem = document.createElement("option");
optElem.value = item;
optElem.text = data[item];
try {
insertsElem.add(optElem, null); // standards compliant browsers
}
catch(ex) {
insertsElem.add(optElem, item+1); // IE only (second paramater is the items position in the list)
}
}
};
Step 3 - Create NativeListBox (code above provided by ZoogieZork above)
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
//populate inserts on listbox create
getInserts();
I have a DataGrid and want a user to select multiple items and click a button to do something with those items (such as delete). When only a few items are selected, the deleting works, but if the user selects all the items without scrolling slowly over them, some of the selected items are null.
I also tried grid.removeSelectedRows(), but that also doesn't work for non-loaded items.
I tried fetching first, too:
grid.store.fetch({count:grid.rowCount,onComplete:dojo.hitch(this,function(){
var items = grid.selection.getSelected();
grid.selection.clear();
if (items.length) {
dojo.forEach(items, function(selectedItem) {
if (selectedItem !== null) {
grid.store.deleteItem(selectedItem); //or do something else
}
});
}
grid.sort();
})});
Even with the fetch, there are still null items, and only the very top and bottom rows actually get deleted.
Is there a way to load the selected items in a grid?
My task was to "extend" selection first item values to the rest of the selection. I've faced similar problem as yours, but finally found a solution:
updateSelected = function() {
//Callback for processing a returned list of items.
function gotSelected(items, request) {
var selectedIndex = paramGrid.selection.selected;
console.debug(selectedIndex);
var firstItem;
var counter = 0;
for (var i=0;i<selectedIndex.length;i++){
if(selectedIndex[i]){
var item = items[i];
if (counter < 1){
firstItem = item;
counter ++;
}else{
paramStore.setValue(item, "valueSPO", firstItem.valueSPO);
paramStore.setValue(item, "valueSPI", firstItem.valueSPI);
paramStore.setValue(item, "valueSM", firstItem.valueSM);
paramStore.setValue(item, "state", firstItem.state);
}
}
}
}
function fetchFailed(error, request) {
console.log("lookup failed.");
console.log(error);
}
paramStore.fetch({
query: {id: '*'},
onComplete: gotSelected,
onError: fetchFailed,
});
}
After this you have to connect this function to a button in addOnLoad:
dojo.connect(button2, "onClick", updateSelected);
OK, I am sorting an XMLListCollection in alphabetical order. I have one issue though. If the value is "ALL" I want it to be first in the list. In most cases this happens already but values that are numbers are being sorted before "ALL". I want "ALL" to always be the first selection in my dataProvider and then the rest alphabetical.
So I am trying to write my own sort function. Is there a way I can check if one of the values is all, and if not tell it to do the regular compare on the values?
Here is what I have:
function myCompare(a:Object, b:Object, fields:Array = null):int
{
if(String(a).toLowerCase() == 'all')
{
return -1;
}
else
if(String(b).toLowerCase() == 'all')
{
return 1;
}
// NEED to return default comparison results here?
}
//------------------------------
var sort:Sort = new Sort();
sort.compareFunction = myCompare;
Is there a solution for what I am trying to do?
The solution from John Isaacks is awesome, but he forgot about "fields" variable and his example doesn't work for more complicated objects (other than Strings)
Example:
// collection with custom objects. We want to sort them on "value" property
// var item:CustomObject = new CustomObject();
// item.name = 'Test';
// item.value = 'Simple Value';
var collection:ArrayCollection = new ArrayCollection();
var s:Sort = new Sort();
s.fields = [new SortField("value")];
s.compareFunction = myCompare;
collection.sort = s;
collection.refresh();
private function myCompare(a:Object, b:Object, fields:Array = null):int
{
if(String((a as CustomObject).value).toLowerCase() == 'all')
{
return -1;
}
else if(String((b as CustomObject).value).toLowerCase() == 'all')
{
return 1;
}
// NEED to return default comparison results here?
var s:Sort = new Sort();
s.fields = fields;
var f:Function = s.compareFunction;
return f.call(null,a,b,fields);
}
Well I tried something out, and I am really surprised it actually worked, but here is what I did.
The Sort class has a private function called internalCompare. Since it is private you cannot call it. BUT there is a getter function called compareFunction, and if no compare function is defined it returns a reference to the internalCompare function. So what I did was get this reference and then call it.
private function myCompare(a:Object, b:Object, fields:Array = null):int
{
if(String(a).toLowerCase() == 'all')
{
return -1;
}
else if(String(b).toLowerCase() == 'all')
{
return 1;
}
// NEED to return default comparison results here?
var s:Sort = new Sort();
var f:Function = s.compareFunction;
return f.call(null,a,b,fields);
}
Thanks guys, this helped a lot. In our case, we needed all empty rows (in a DataGrid) on the bottom. All non-empty rows should be sorted normally. Our row data is all dynamic Objects (converted from JSON) -- the call to ValidationHelper.hasData() simply checks if the row is empty. For some reason the fields sometimes contain the dataField String value instead of SortFields, hence the check before setting the 'fields' property:
private function compareEmptyAlwaysLast(a:Object, b:Object, fields:Array = null):int {
var result:int;
if (!ValidationHelper.hasData(a)) {
result = 1;
} else if (!ValidationHelper.hasData(b)) {
result = -1;
} else {
if (fields && fields.length > 0 && fields[0] is SortField) {
STATIC_SORT.fields = fields;
}
var f:Function = STATIC_SORT.compareFunction;
result = f.call(null,a,b,fields);
}
return result;
}
I didn't find these approaches to work for my situation, which was to alphabetize a list of Strings and then append a 'Create new...' item at the end of the list.
The way I handled things is a little inelegant, but reliable.
I sorted my ArrayCollection of Strings, called orgNameList, with an alpha sort, like so:
var alphaSort:Sort = new Sort();
alphaSort.fields = [new SortField(null, true)];
orgNameList.sort = alphaSort;
orgNameList.refresh();
Then I copied the elements of the sorted list into a new ArrayCollection, called customerDataList. The result being that the new ArrayCollection of elements are in alphabetical order, but are not under the influence of a Sort object. So, adding a new element will add it to the end of the ArrayCollection. Likewise, adding an item to a particular index in the ArrayCollection will also work as expected.
for each(var org:String in orgNameList)
{
customerDataList.addItem(org);
}
Then I just tacked on the 'Create new...' item, like this:
if(userIsAllowedToCreateNewCustomer)
{
customerDataList.addItem(CREATE_NEW);
customerDataList.refresh();
}