https://jsfiddle.net/4byyuqtc/1/
I'm looking to have the ui-grid select all "children" under a grouping when the grouping line is selected. In this case Kit Kat(1), Mr. Goodbar(1), Krackel(2) and ultimately selecting the actual records (the non bold lines). One would expect that when selecting a parent in a grouping all it's children would get selected as well.
Currently when selecting the 1 grouping above the actual records in the data (the non bold lines) it does select those actual records with the following code:
$scope.gridApi.selection.on.rowSelectionChanged($scope, function (rowChanged) {
console.log(rowChanged.treeLevel);
if (typeof (rowChanged.treeLevel) !== 'undefined' && rowChanged.treeLevel > -1) {
// this is a group header
children = $scope.gridApi.treeBase.getRowChildren(rowChanged);
console.log(children);
children.forEach(function (child) {
if (rowChanged.isSelected) {
$scope.gridApi.selection.selectRow(child.entity);
} else {
$scope.gridApi.selection.unSelectRow(child.entity);
}
});
}
});
I'm not experienced enough with ui-grid at this point to figure out how to cycle through children of the selected line and select all of them.
[EDIT]
With Paul's code below it doesn't select the groupings but it's closer. This screenshot is me selecting the first 337 record. Notice it selects that record and all the lowest child records (which is good because ultimately those are the ones that matter) but visually the grouped records (MFG and Item Desc group) aren't selected and need to be as the user won't ever open the lowest data records so they need to see the groups selected.
I checked the documentation and I don't think there's any exposed API Method. You could recursively select/deselect rows as a solution. Please try out the example below.
$scope.gridApi.selection.on.rowSelectionChanged($scope, function (rowChanged) {
console.log(rowChanged.treeLevel);
if (typeof(rowChanged.treeLevel) !== 'undefined' && rowChanged.treeLevel > -1) {
var children = $scope.gridApi.treeBase.getRowChildren(rowChanged);
selectChildren(children, rowChanged.isSelected);
}
});
function selectChildren(gridRows, selected) {
if (gridRows && gridRows.length > 0) {
gridRows.forEach(function (child) {
if (selected) {
$scope.gridApi.selection.selectRow(child.entity);
} else {
$scope.gridApi.selection.unSelectRow(child.entity);
}
var children = $scope.gridApi.treeBase.getRowChildren(child);
selectChildren(children, selected); //recursively select/de-select children
});
}
}
Here's a working Plunkr: http://plnkr.co/edit/XsoEUncuigj9Cad1vP5E?p=preview
Handling automatic deselection is a bit more tricky though as it seems the api doesn't handle that really well.
UPDATE
So I checked the jsFiddle you shared and managed to get it working with a slight tweak.
I modified the selectionHandler to the following:
onRegisterApi: function(gridApi) {
$scope.gridApi = gridApi;
$scope.gridApi.selection.on.rowSelectionChanged($scope, function(rowChanged) {
if (rowChanged.treeNode.parentRow) { //Added this parent row selection
rowChanged.treeNode.parentRow.setSelected(rowChanged.isSelected);
}
console.log(rowChanged.treeLevel);
if (typeof(rowChanged.treeLevel) !== 'undefined' && rowChanged.treeLevel > -1) {
var children = $scope.gridApi.treeBase.getRowChildren(rowChanged);
selectChildren(children, rowChanged.isSelected);
}
});
Please see this fork of your code: https://jsfiddle.net/1eg5v77w/
The downside with this is that if you select a low level entry (one without children) it will still select its parent. If you really really want this to work as well, you'll have to access the DOM and make some ugly checks.
$scope.gridApi.selection.on.rowSelectionChanged($scope, function(rowChanged, $event) {
var wasHeaderRowClicked = true;
try { //This can be written more beautifully if you used jQuery. But I would still be against it as it relies on the class of the ui-grid never changing when you update your ui-grid version.
wasHeaderRowClicked = $event
.srcElement
.parentElement
.parentElement
.parentElement
.previousElementSibling
.firstChild
.firstChild
.firstChild
.getAttribute('class') === 'ui-grid-icon-minus-squared';
} catch(err) { console.log('Couldnt determine if header row was clicked'); }
if (rowChanged.treeNode.parentRow && wasHeaderRowClicked) {
rowChanged.treeNode.parentRow.setSelected(rowChanged.isSelected);
}
console.log(rowChanged.treeLevel);
if (typeof(rowChanged.treeLevel) !== 'undefined' && rowChanged.treeLevel > -1) {
var children = $scope.gridApi.treeBase.getRowChildren(rowChanged);
selectChildren(children, rowChanged.isSelected);
}
});
Here is the fiddle: https://jsfiddle.net/Lf8p7Luk/1/
I'd also like to add, thanks to this post, that according to the UI-Grid documentation: Group header rows cannot be edited, and if using the selection feature, cannot be selected. They can, however, be exported.
So it is intentional that it's so difficult to get this to work because it's not the intended design. My recommendation would be to alter your logic to either use Tree Levels or get around the selection logic because even though my fork is currently selecting everything, you will most likely run into other issues down the road. For example: I couldn't get automatic deselection to work in the grid when you click on another group header.
If you still have the issue take a look with this..
https://github.com/angular-ui/ui-grid/issues/3911
Related
I have a tablesorter table that contains a 'Category' column. My searches reveal many different ways to filter on various types of columns but they all boil down to a single filter criteria.
What I have is a section on the page that lists all the categories with checkboxes, the idea is to allow the user to select which categories they want to look at (there are 10 different categories) and have these selected categories be applied to filter the rows in the tablesorter.
I could always brute force this with a post back to my controller and return a model with a filtered set of rows, but if there is a sensible way to accomplish this on the client side I would greatly appreciate anyones input on where to begin tackling a problem like this.
This is my first crack at it. So assume I have a bunch of checkboxes representing the categories to filter on. The class 'tablesorter' is what I called the table containing the data. I added an id attribute to the cell which has the category in it. I did it this way because I do not want the id to be visible on screen. At the end I refresh the zebra striping after hiding/unhiding rows. If there is a better way to do this, I'm all ears (I am a total newb to javascript/jquery/tablesorter).
function FilterCompanies() {
$(':checkbox:not(:checked)').each(function () {
var unselectedCategoryId = $(this).attr('id').substring(3);
$('.tablesorter > tbody > tr > td[id]').each(function() {
if ($(this).attr('id') == unselectedCategoryId) {
$($(this).parent()).addClass('hidden');
}
});
});
$(':checkbox:checked').each(function () {
var unselectedCategoryId = $(this).attr('id').substring(3);
$('.tablesorter > tbody > tr > td[id]').each(function () {
if ($(this).attr('id') == unselectedCategoryId) {
$($(this).parent()).removeClass('hidden');
}
});
});
$(".tablesorter").trigger("applyWidgets")
}
Here's what it looks like:
Couple of days ago I found this interesting post at http://www.smartjava.org/content/drag-and-drop-angularjs-using-jquery-ui and applied it into my website. However when I progressively using it there is a bug I identified, basically you can not move an item directly from one div to another's bottom, it has to go through the parts above and progress to the bottom. Anyone can suggest where does it goes wrong? The example is at http://www.smartjava.org/examples/dnd/double.html
Troubling me for days already.....
I did this a bit differently. Instead of attaching a jquery ui element inside the directive's controller, I instead did it inside the directive's link function. I came up with my solution, based on a blog post by Ben Farrell.
Note, that this is a Rails app, and I am using the acts_as_list gem to calculate positioning.
app.directive('sortable', function() {
return {
restrict: 'A',
link: function(scope, elt, attrs) {
// the card that will be moved
scope.movedCard = {};
return elt.sortable({
connectWith: ".deck",
revert: true,
items: '.card',
stop: function(evt, ui) {
return scope.$apply(function() {
// the deck the card is being moved to
// deck-id is an element attribute I defined
scope.movedCard.toDeck = parseInt(ui.item[0].parentElement.attributes['deck-id'].value);
// the id of the card being moved
// the card id is an attribute I definied
scope.movedCard.id = parseInt(ui.item[0].attributes['card-id'].value);
// edge case that handles a card being added to the end of the list
if (ui.item[0].nextElementSibling !== null) {
scope.movedCard.pos = parseInt(ui.item[0].nextElementSibling.attributes['card-pos'].value - 1);
} else {
// the card is being added to the very end of the list
scope.movedCard.pos = parseInt(ui.item[0].previousElementSibling.attributes['card-pos'].value + 1);
}
// broadcast to child scopes the movedCard event
return scope.$broadcast('movedCardEvent', scope.movedCard);
});
}
});
}
};
});
Important points
I utilize card attributes to store a card's id, deck, and position, in order to allow the jQuery sortable widget to grab onto.
After the stop event is called, I immediately execute a scope.$apply function to get back into, what Misko Hevery call,s the angular execution context.
I have a working example of this in action, up in a GitHub Repo of mine.
I have a page, which is used for building queries and running them against different entities (Kind of a query builder/generic search).
The results are displayed in JQGrid, so effectively the same grid will be used for rendering results from different entities.
This results grid has to support context menus, which will differ for each entity. So I need a way to change the context menu as per the entity. Each entity may have different number of menu items in context menu and each item may respond in a different manner (sometimes an alert, sometimes an action spawning in a different tab).
Rendering different menus (through li) is not an issue but attaching the methods to the li is proving to be a challenge. Any pointers will be highly appreciated.
I am using jquery.contextmenu-ui.js .
Following is from a sample that I picked from their (JQGrid) site
function initGrid() {
$("#EntityGrid").contextMenu('cMenu'
,{
bindings: { /* I would like to avoid this and pass all the actions to one method*/
'edit': function (t) {
editRow();
},
'add': function (t) {
addRow();
},
'del': function (t) {
delRow();
}
},
onContextMenu: function (event, menu) {
var rowId = $(event.target).parent("tr").attr("id")
var grid = $("#EntityGrid");
grid.setSelection(rowId);
return true;
}
}
);
}
Thanks,
Avinash
You can use onShowMenu callback of contextMenu instead of static binding using bindings. In the same way the menuId used as the first parameter of contextMenu could be the id of dynamically created div with empty <ul>. The onShowMenu has the form
onShowMenu: function (e, $menu) {
// here one can clear `<ul>` child of $menu
// and append it with "<li>" items
return $menu;
}
In the answer you will find an example of the code which build menu dynamically.
Currently I am working on a project for which I use the jQuery UI Accordion.
Therefore I initialise the accordion on an element by doing
<div id="accordion"></div>
$('#accordion').accordion({
collapsible: true,
active: false,
heightStyle: "content"
});
After init the accordion I append some data coming from an AJAX request. (depends on user interaction)
In a simplified jsfiddle - which does exact the same thing as the ajax call - you can see how this looks like.
So far it seems to be working quite well but there is one problem I face.
In my initialisation I say that I want all panels to be closed but after calling refresh on the accordion everything of those settings seems to be gone and one panel opens.
Note that I implemented jQuery UI v1.10.2 in my fiddle. Update notes say
The refresh method will now recognize panels that have been added or removed. This brings accordion in line with tabs and other widgets that parse the markup to find changes.
Well it does but why has it to "overwrite" the settings I defined for this accordion?
I also thought about the possibility that it might be wrong to create the accordion on an empty <div> so I tested it with a given entry and added some elements afterwards.
But the jsfiddle shows exactly the same results.
In a recent SO thread I found someone who basically does the same thing as I do but in his jsfiddle he faces the same "issue".
He adds a new panel and the first panel opens after the refresh.
My current solution for this issue is to destroy the accordion and recreate it each time there's new content for it.
But this seems quite rough to me and I thought the refresh method solves the need to destroy the accordion each time new content gets applied.
See the last jsfiddle
$(document).ready(function () {
//variable to show "new" content gets appended correctly
var foo = 1;
$('#clickMe').on('click', function () {
var data = '';
for (var i = 0; i < 3; i++) {
data += '<h3>title' + foo + '</h3><div>content</div>';
foo++;
}
if ($('#accordion').hasClass('ui-accordion')) {
$('#accordion').accordion('destroy');
}
$('#accordion').empty().append(data).accordion({
collapsible: true,
active: false,
heightStyle: "content"
});
});
});
Unfortunately it is not an option for me to change the content of the given 3 entries because the amount of panels varies.
So my questions are the one in the title and if this behaviour is wanted like that or if anybody faces the same problem?
For the explanation of this behaviour, have a look in the refresh() method of the jquery-ui accordion widget, the problem you are facing is at line 10 :
refresh: function() {
var options = this.options;
this._processPanels();
// was collapsed or no panel
if ((options.active === false && options.collapsible === true) || !this.headers.length) {
options.active = false;
this.active = $();
// active false only when collapsible is true
} if (options.active === false) {
this._activate(0); // <-- YOUR PROBLEM IS HERE
// was active, but active panel is gone
} else if (this.active.length && !$.contains(this.element[0], this.active[0])) {
// all remaining panel are disabled
if (this.headers.length === this.headers.find(".ui-state-disabled").length) {
options.active = false;
this.active = $();
// activate previous panel
} else {
this._activate(Math.max(0, options.active - 1));
}
// was active, active panel still exists
} else {
// make sure active index is correct
options.active = this.headers.index(this.active);
}
this._destroyIcons();
this._refresh();
}
Given a certain event (say, a button click), I'd like to disable Chosen.js for a specific select box.
Is this possible?
I had a similar requirement although it didn't require a button click, I just wanted to enable / disable Chosen for specific select boxes.
My solution is for each select element that you don't want to use Chosen with, add the CSS class 'no-chzn'. Then in chosen.jquery.js, add the following lines to the constructor (this is near line 533 in version 1.1.0):
$.fn.extend({
chosen: function(options) {
if (!AbstractChosen.browser_is_supported()) {
return this;
}
return this.each(function(input_field) {
var $this, chosen;
$this = $(this);
if ($this.parent().hasClass("chosen-disable")) { return; } // Just add this line!
chosen = $this.data('chosen');
if (options === 'destroy' && chosen) {
chosen.destroy();
} else if (!chosen) {
$this.data('chosen', new Chosen(this, options));
}
});
}
});
For example you have a select with class no-chosen and you do this:
$("select:not('.no-chosen')").chosen();
It means it takes all selects except the one with no-chosen class.