Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of basic context menu #258

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions js/src/qgrid.css
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,28 @@
border-left: 1px solid #cccccc;
}

/* Context menu */

.q-grid-container #contextMenu {
background: rgb(245, 245, 245);
border: 1px solid gray;
padding: 2px;
display: inline-block;
min-width: 100px;
-moz-box-shadow: 2px 2px 2px silver;
-webkit-box-shadow: 2px 2px 2px silver;
z-index: 99999;
}

.q-grid-container #contextMenu li {
padding: 4px 4px 4px 14px;
list-style: none;
}

.q-grid-container #contextMenu li:hover {
background-color: #cccccc;
}

/* Filter dropdowns */

.q-grid-container .grid-filter {
Expand Down
57 changes: 57 additions & 0 deletions js/src/qgrid.widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,31 @@ class QgridView extends widgets.DOMWidgetView {
);
this.grid_elem.data('slickgrid', this.slick_grid);

if (this.grid_options.context_menu){
this.slick_grid.onContextMenu.subscribe((e) => {

//calculate x,y position for the context menu
e.preventDefault();
var bounds = this.$el[0].getBoundingClientRect();
var x = e.pageX - bounds.left;
var y = e.pageY - bounds.top;

var cell = this.slick_grid.getCellFromEvent(e);
if (cell.cell<=0)
return;

var column = this.columns[cell.cell].name;
var data_item = this.slick_grid.getDataItem(cell.row);
this.send({
type: 'show_context_menu',
x,
y,
'row_index': data_item.row_index,
column
})
});
}

if (this.grid_options.forceFitColumns){
this.grid_elem.addClass('force-fit-columns');
}
Expand Down Expand Up @@ -789,6 +814,8 @@ class QgridView extends widgets.DOMWidgetView {
} else if (msg.col_info) {
var filter = this.filters[msg.col_info.name];
filter.handle_msg(msg);
} else if (msg.type == 'show_context_menu'){
this.show_context_menu(msg.x, msg.y, msg.index, msg.column, msg.items)
}
}

Expand All @@ -802,6 +829,36 @@ class QgridView extends widgets.DOMWidgetView {
}
}

show_context_menu (x,y, index, column, items) {
if (!this.context_elem) {
this.context_elem = $(`<ul id='contextMenu' style='display:none;position:absolute'</ul>`).appendTo(this.$el);
}
this.context_elem.empty();
$.each(items, (k,v) => {$(`<li key=${k}>${v}</li>`).appendTo(this.context_elem)});

this.context_elem
.data({index, column})
.css("top", y)
.css("left", x)
.show();

this.context_elem.find('li').one("click", e => {
var index = this.context_elem.data("index");
var column = this.context_elem.data("column");
var key = $(e.target).attr('key')
this.send({
'type': 'context_menu_item_clicked',
key,
index,
column
})
});

$("body").one("click", () => {
this.context_elem.hide();
});
}

/**
* Update the size of the dataframe.
*/
Expand Down
50 changes: 48 additions & 2 deletions qgrid/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,8 @@ def show_grid(data_frame,
grid_options=None,
column_options=None,
column_definitions=None,
row_edit_callback=None):
row_edit_callback=None,
context_menu = None):
"""
Renders a DataFrame or Series as an interactive qgrid, represented by
an instance of the ``QgridWidget`` class. The ``QgridWidget`` instance
Expand Down Expand Up @@ -377,6 +378,27 @@ def show_grid(data_frame,
particular row's values, keyed by column name. The callback should
return True if the provided row should be editable, and False
otherwise.
context_menu : dict
A dict contains two callables to be used for handling the context menu.
Items in the context menu are dynamically generated based on the cell.

``
context_menu = {
'items_callback' : ... callable(index, column)
'click_callback' : ... callable(index, column, key)
}
``

The ``items_callback`` takes the follwoing parameters:
* **index** The index of the row that contains the clicked cell.
* **column** The name of the column that contains the clicked cell.

And it returns a dictionary of key-value strings to be shown in the
context menu.

Then ``click_callback`` takes one more parameter in addition to the
previous parameters:
* **key** The key of the context menu item that was clicked.


Notes
Expand Down Expand Up @@ -487,6 +509,12 @@ def show_grid(data_frame,
options = defaults.grid_options.copy()
options.update(grid_options)
grid_options = options

if context_menu:
grid_options['context_menu'] = True
else:
context_menu = {}

if not isinstance(grid_options, dict):
raise TypeError(
"grid_options must be dict, not %s" % type(grid_options)
Expand All @@ -508,7 +536,8 @@ def show_grid(data_frame,
column_options=column_options,
column_definitions=column_definitions,
row_edit_callback=row_edit_callback,
show_toolbar=show_toolbar)
show_toolbar=show_toolbar,
context_menu=context_menu)


PAGE_SIZE = 100
Expand Down Expand Up @@ -607,6 +636,7 @@ class can be constructed directly but that's not recommended because
column_options = Dict({})
column_definitions = Dict({})
row_edit_callback = Instance(FunctionType, sync=False, allow_none=True)
context_menu = Dict({})
show_toolbar = Bool(False, sync=True)
id = Unicode(sync=True)

Expand Down Expand Up @@ -1549,6 +1579,22 @@ def _handle_qgrid_msg_helper(self, content):
'name': 'filter_changed',
'column': content['field']
})
elif content['type'] == 'show_context_menu':
x, y, row_index, column = content['x'], content['y'], content['row_index'], content['column']
index = self._df.index[row_index]
items = self.context_menu['items_callback'](index, column)
if items and isinstance(items, dict):
self.send({
'type': 'show_context_menu',
'x': x,
'y': y,
'index': index,
'column': column,
'items': items
})
elif content['type'] == 'context_menu_item_clicked':
index, column, key = content['index'], content['column'], content['key']
self.context_menu['click_callback'](index, column, key)

def _notify_listeners(self, event):
# notify listeners at the module level
Expand Down