Handsontable - Unable to keep html element created by a custom renderer visible - handsontable

I am using the open source version of handsontable (version 0.29.2). I created a custom renderer that creates a hidden SPAN element/icon on every row. When input fails validation, I use jQuery to programmatically unhide/show the SPAN tag/icon so that it appears in the right-hand side of the cell. It works great, but unfortunately when I enter an invalid value into another cell, the icon from the first cell disappears. The preferred behavior is to have all of the icons visible in cells where a validation issue exists.
Question: Is there a way to keep all of the icons visible?
If this is not possible, is there a different way in handsontable to display an image after validation? As you can see from the code below (and my jsfiddle example), I am not using the built-in handsontable validation hooks. With the built-in validation, I can't add an icon like I want - I can only override the default style of an invalid cell by using invalidCellClassName.
I have created a simple example with instructions demonstrating my issue:
http://jsfiddle.net/4g3a5kqc/15/
var data = [
["1", "abc"],
["2", "def"],
["3", "ghi"],
["4", "jkl"]
],
container = document.getElementById("example"),
hot1;
// This function is a custom renderer that creates a hidden SPAN element/
// icon. In this example, when a user changes the value, the SPAN element
// icon will appear.
function customRenderer(instance, td, row, col, prop, value, cellProperties) {
td.innerHTML = value +
'<span id="account-code-error-' + row + '-' + col + '" class="account-code-error ' +
'glyphicon glyphicon-exclamation-sign text-warning jzb-icon-md pull-right" ' +
'style="font-size: large; cursor: pointer; display: none;"></span>';
}
var hot1 = new Handsontable(container, {
data: data,
rowHeaders: true,
colHeaders: true,
stretchH: 'all',
cells:
function (row, col, prop) {
var cellProperties = {};
if (col == 0) {
cellProperties.renderer = customRenderer;
}
return cellProperties;
}
});
hot1.addHook('afterChange', afterChange);
// Show the SPAN tag with the icon
// in the right-hand side of the cell.
function afterChange(changes, source) {
console.log(changes, source);
if (source == 'edit' || source == 'autofill') {
$.each(changes,
function (index, element) {
var change = element;
var rowIndex = change[0];
var columnIndex = change[1];
var oldValue = change[2];
var newValue = change[3];
console.log(oldValue, newValue, rowIndex, columnIndex, change);
if (columnIndex != 0) {
return;
}
if (newValue >= 0) {
return;
}
var cellProperties = hot1.getCellMeta(rowIndex, hot1.propToCol(columnIndex));
var td = hot1.getCell(rowIndex, columnIndex, true);
var span = td.getElementsByTagName("span");
$("#" + span[0].id).show();
});
}
}

Due to customRenderer() being called after every change we have to store somewhere cells with spans visible and check for it at the rendering. On the other hand if the span should not be visible (input is valid) we need to remove it from the array of cells wit visible spans. Working fiddle:
http://jsfiddle.net/8vdwznLs/
var data = [
["1", "abc"],
["2", "def"],
["3", "ghi"],
["4", "jkl"]
],
container = document.getElementById("example"),
hot1,
visibleSpans = [];
// This function is a custom renderer that creates a hidden SPAN element/
// icon. In this example, when a user changes the value, the SPAN element
// icon will appear.
function customRenderer(instance, td, row, col, prop, value, cellProperties) {
if (visibleSpans.indexOf(td) > -1) {
td.innerHTML = value +
'<span id="account-code-error-' + row + '-' + col + '" class="account-code-error ' +
'glyphicon glyphicon-exclamation-sign text-warning jzb-icon-md pull-right" ' +
'style="font-size: large; cursor: pointer;"></span>';
} else {
td.innerHTML = value +
'<span id="account-code-error-' + row + '-' + col + '" class="account-code-error ' +
'glyphicon glyphicon-exclamation-sign text-warning jzb-icon-md pull-right" ' +
'style="font-size: large; cursor: pointer; display: none;"></span>';
}
}
var hot1 = new Handsontable(container, {
data: data,
rowHeaders: true,
colHeaders: true,
stretchH: 'all',
cells:
function (row, col, prop) {
var cellProperties = {};
if (col == 0) {
cellProperties.renderer = customRenderer;
}
return cellProperties;
}
});
hot1.addHook('afterChange', afterChange);
// Show the SPAN tag with the icon
// in the right-hand side of the cell.
function afterChange(changes, source) {
console.log(changes, source);
if (source == 'edit' || source == 'autofill') {
$.each(changes,
function (index, element) {
var change = element;
var rowIndex = change[0];
var columnIndex = change[1];
var oldValue = change[2];
var newValue = change[3];
var td = hot1.getCell(rowIndex, columnIndex, true);
console.log(oldValue, newValue, rowIndex, columnIndex, change);
if (columnIndex != 0) {
return;
}
if (newValue >= 0) {
var indexOfSpan = visibleSpans.indexOf(td);
if (indexOfSpan > -1) {
visibleSpans.splice(indexOfSpan, 1);
hot1.render();
return;
}
return;
}
var cellProperties = hot1.getCellMeta(rowIndex, hot1.propToCol(columnIndex));
visibleSpans.push(td);
var span = td.getElementsByTagName("span");
span[0].setAttribute('style', '');
});
}
}

Related

jqGrid afterclickPgButtons skip the line where isLeaf non is TRUE?

I use jqGrid 4.9.3-pre - free jqGrid by Oleg.
I use:
contextmenu
treegrid
form Edit
multiselect: false
Code
treeGrid:true,
ExpandColumn:'name',
treedatatype:"json",
treeGridModel:"adjacency",
treeReader:{
level_field: "level",
parent_id_field: "parent",
leaf_field: "isLeaf",
expanded_field: "expanded",
loaded:true,
},
loadonce: false
How to If isLeaf is TRUE skip the line and go to the next line where isLeaf non is TRUE?
Navigation buttons of Edit form have no special interface which could allow to skip some rows, but one can use onclickPgButtons to prevent the navigation to the next/previous row and to simulate the click on the same button immediately after that. It's important to understand that jqGrid contains hidden field in the form with the id="id_g", which will be used by form editing as the rowid of the current editing row. Thus one can change the value of the hidden field before simulation of the click.
The corresponding implementation of the onclickPgButtons callback could be the following:
onclickPgButtons: function (buttonName, $form, rowid) {
var $self = $(this),
iRow = $self.jqGrid("getGridRowById", rowid).rowIndex,
isLeaf = $self.jqGrid("getGridParam", "treeReader").leaf_field,
rows = this.rows,
nRows = rows.length,
iInc = buttonName === "next" ? 1 : -1,
isNextRowVisibleLeaf = function () { // iRow - the current row
var $nextRow = $(rows[iRow + iInc]),
rowidNext = $nextRow.attr("id");
if (rowidNext != null) {
var nextItem = $self.jqGrid("getLocalRow", rowidNext);
if (nextItem != null && nextItem[isLeaf] && $nextRow.css("display") !== "none") {
return true;
}
}
return false;
},
$button = $(buttonName === "next" ? "#nData" : "#pData");
if (isNextRowVisibleLeaf()) {
return true; // nothing to do
}
// we need to fix the row, which the next row is visible leaf
while (iRow < nRows && iRow > 0) {
iRow += iInc;
if (isNextRowVisibleLeaf()) {
// set the value of hidden field of the form
// to the id of the found row and simulate the click
// on the same navigation button
$form.find("#id_g").val($(rows[iRow]).attr("id"));
setTimeout(function () {
$button.click();
}, 50);
return false;
}
}
return false;
}
See the demo.

Determine whether user clicking scrollbar or content (onclick for native scroll bar)

I'm trying to create custom events in JQuery that are supposed to detect when a scrollbar is clicked1.
I know there's lots of text, but all my questions are boldfaced and there's a JSFiddle example you can work on straight away.
Because I haven't found any built in functionality for this,
I had to create a hasScroll function, checking if the element has a scrollbar,
$.fn.hasScroll = function(axis){
var overflow = this.css("overflow"),
overflowAxis;
if(typeof axis == "undefined" || axis == "y") overflowAxis = this.css("overflow-y");
else overflowAxis = this.css("overflow-x");
var bShouldScroll = this.get(0).scrollHeight > this.innerHeight();
var bAllowedScroll = (overflow == "auto" || overflow == "visible") ||
(overflowAxis == "auto" || overflowAxis == "visible");
var bOverrideScroll = overflow == "scroll" || overflowAxis == "scroll";
return (bShouldScroll && bAllowedScroll) || bOverrideScroll;
};
and an inScrollRange function, checking if the click performed was within the scroll range.
var scrollSize = 18;
function inScrollRange(event){
var x = event.pageX,
y = event.pageY,
e = $(event.target),
hasY = e.hasScroll(),
hasX = e.hasScroll("x"),
rX = null,
rY = null,
bInX = false,
bInY = false
if(hasY){
rY = new RECT();
rY.top = e.offset().top;
rY.right = e.offset().left + e.width();
rY.bottom = rY.top +e.height();
rY.left = rY.right - scrollSize;
//if(hasX) rY.bottom -= scrollSize;
bInY = inRect(rY, x, y);
}
if(hasX){
rX = new RECT();
rX.bottom = e.offset().top + e.height();
rX.left = e.offset().left;
rX.top = rX.bottom - scrollSize;
rX.right = rX.left + e.width();
//if(hasY) rX.right -= scrollSize;
bInX = inRect(rX, x, y);
}
return bInX || bInY;
}
Are all scrollbar sizes uniform? E.g in Firefox and IE it's 18px.
Assuming there are no customized scrollbars, is there any extra padding or sizes in some browsers?
These functions all perform as intended (from what I can discern).
Making custom events was a bit trickier, but I got it to work somewhat. The only problem is that if the element clicked has a mousedown/up event attached to it, that will be triggered as well.
I can't seem to stop the other events from triggering while simultaneously triggering, what I call, the mousedownScroll/mouseupScroll events.
$.fn.mousedownScroll = function(fn, data){
if(typeof fn == "undefined" && typeof data == "undefined"){
$(this).trigger("mousedownScroll");
return;
}
$(this).on("mousedownScroll", data, fn);
};
$.fn.mouseupScroll = function(fn, data){
if(typeof fn == "undefined" && typeof data == "undefined"){
$(this).trigger("mouseupScroll");
return;
}
$(this).on("mouseupScroll", data, fn);
};
$(document).on("mousedown", function(e){
if(inScrollRange(e)){
$(e.target).trigger("mousedownScroll");
}
});
$(document).on("mouseup", function(e){
if(inScrollRange(e)){
$(e.target).trigger("mouseupScroll");
}
});
$("selector").mousedown(function(e){
console.log("Clicked content."); //Fired when clicking scroller as well
});
$("selector").mousedownScroll(function(e){
console.log("Clicked scroller.");
});
How do I stop the other "click" events from triggering?
While I'm asking, please feel free to optimize the code as much as possible.
Here's a JSFiddle to mess around with.
The reason I'm making this is because of a bigger plugin I'm developing. It's got a custom context menu that is showing up when I right click one of the scrollers. I don't want that. So I thought I should make an event that checks for scroll clicks (mouseup/downs) and then prevent the context menu from being displayed. In order to do that though, I need the scroll click to come before the normal click, and also, if possible, stop the normal clicks from firing.
I'm just thinking out loud here but maybe there's a way to get all the functions that are bound to the element and then switch the order in which they were added? I know that functions are executed in the order they were added (1st added 1st called), so, if I could tap into that process, perhaps the whole "registering" of the event to JQuery could just be inserted before the click events.
1 can only use mousedown/mouseup because click doesn't trigger when clicking on a scrollbar. If this is false, please provide a working example/code
Solved:
A shortest scrollbar click detection I could come up with, tested on IE, Firefox, Chrome.
var clickedOnScrollbar = function(mouseX){
if( $(window).outerWidth() <= mouseX ){
return true;
}
}
$(document).mousedown(function(e){
if( clickedOnScrollbar(e.clientX) ){
alert("clicked on scrollbar");
}
});
Working example:
https://jsfiddle.net/s6mho19z/
Use following solution to detect if user clicked mouse over element's scrollbar. Didn't test how it works with window's scrollbar. I guess Pete's solution works better with window scrolls.
window.addEventListener("mousedown", onMouseDown);
function onMouseDown(e) {
if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight)
{
// mouse down over scroll element
}
}
You may probably use this hack.
You could try hijacking the mousedown and mouseup events and avoiding them when click on a scrollbar with your custom powered function.
$.fn.mousedown = function(data, fn) {
if ( fn == null ) {
fn = data;
data = null;
}
var o = fn;
fn = function(e){
if(!inScrollRange(e)) {
return o.apply(this, arguments);
}
return;
};
if ( arguments.length > 0 ) {
return this.bind( "mousedown", data, fn );
}
return this.trigger( "mousedown" );
};
And the inverse for mousedownScroll and mouseupScroll events.
$.fn.mousedownScroll = function(data, fn) {
if ( fn == null ) {
fn = data;
data = null;
}
var o = fn;
fn = function(e){
if(inScrollRange(e)) {
e.type = "mousedownscroll";
return o.apply(this, arguments);
}
return;
};
if ( arguments.length > 0 ) {
return this.bind( "mousedown", data, fn );
}
return this.trigger( "mousedown" );
};
By the way, I think the scrollbar width is an OS setting.
Ensure that the content of your scollarea completely [over]fills the parent div.
Then, you can differentiate between clicks on your content and clicks on your container.
html:
<div class='test container'><div class='test content'></div></div>
<div id="results">please click</div>
css:
#results {
position: absolute;
top: 110px;
left: 10px;
}
.test {
position: absolute;
left: 0;
top: 0;
width: 100px;
height: 100px;
background-color: green;
}
.container {
overflow: scroll;
}
.content {
background-color: red;
}
js:
function log( _l ) {
$("#results").html( _l );
}
$('.content').on( 'mousedown', function( e ) {
log( "content-click" );
e.stopPropagation();
});
$('.container').on( 'mousedown', function( e ) {
var pageX = e.pageX;
var pageY = e.pageY;
log( "scrollbar-click" );
});
http://codepen.io/jedierikb/pen/JqaCb
I had the same problem in a previous project, and i recommend this solution. It's not very clean but it works and i doubt we can do much better with html. Here are the two steps of my solution:
1. Measure the width of the scrollbar on your Desktop environment.
In order to achieve this, at application startup, you perform the following things:
Add the following element to the body:
<div style='width: 50px; height: 50px; overflow: scroll'><div style='height: 1px;'/></div>
Measure the with of the inner div of the previously added element with jQUery's .width(), and store the width of the scrollbar somewhere (the width of the scollbar is 50 - inner div's with)
Remove the extra element used to measure scrollbar (now that you have the result, remove the element that you added to the body).
All these steps should not be visible by the user and you have the width of the scrollbar on your OS
For example, you can use this snippet:
var measureScrollBarWidth = function() {
var scrollBarMeasure = $('<div />');
$('body').append(scrollBarMeasure);
scrollBarMeasure.width(50).height(50)
.css({
overflow: 'scroll',
visibility: 'hidden',
position: 'absolute'
});
var scrollBarMeasureContent = $('<div />').height(1);
scrollBarMeasure.append(scrollBarMeasureContent);
var insideWidth = scrollBarMeasureContent.width();
var outsideWitdh = scrollBarMeasure.width();
scrollBarMeasure.remove();
return outsideWitdh - insideWidth;
};
2. Check if a click is on the scrollbar.
Now that you have the width of the scrollbar, you can with the coordinates of the event compute the coordinates of the event relative to the scrollbar's location rectangle and perfom awesome things...
If you want to filter the clicks, you can return false in the handler to prevent their propagation.
There are many answers here that involve event.clientX, element.clientHeight, etc. They are all wrong. Do not use them.
As has been discussed above, there are platforms where the overflow: scroll scrollbars appear as overlays, or you may have forced it with overflow: overlay.
Macs may switch scrollbars between persistent and overlay by plugging or unplugging a mouse. This shoots down the "measure on startup" technique.
Vertical scrollbars appear on the left side with right to left reading order. This breaks comparing client width unless you have a bunch of special logic for right to left reading order that I bet will break because you're probably not testing RTL consistently.
You need to look at event.target. If necessary, use an inner element that occupies all of the client area of the scroll element, and see if event.target is that element or a descendant of it.
It should be pointed out that on Mac OSX 10.7+, there are not persistant scroll bars. Scroll bars appear when you scroll, and disappear when your done. They are also much smaller then 18px (they are 7px).
Screenshot:
http://i.stack.imgur.com/zdrUS.png
I'll submit my own answer and accept Alexander's answer, because it made it work perfectly, and upvote Samuel's answer, because it correctly calculates the scrollbar width, which is what I needed as well.
That being said, I decided to make two independent events instead of trying to overwrite/override JQuery's mousedown event.
This gave me the flexibility I needed without messing with JQuery's own events, and was quite easy to do.
mousedownScroll
mousedownContent
Below are the two implementations using Alexanders, and my own.
Both work as I originally intended them to, but the former is probably the best.
Here's a JSFiddle that implements Alexander's answer + Samuel's answer.
$.fn.hasScroll = function(axis){
var overflow = this.css("overflow"),
overflowAxis;
if(typeof axis == "undefined" || axis == "y") overflowAxis = this.css("overflow-y");
else overflowAxis = this.css("overflow-x");
var bShouldScroll = this.get(0).scrollHeight > this.innerHeight();
var bAllowedScroll = (overflow == "auto" || overflow == "visible") ||
(overflowAxis == "auto" || overflowAxis == "visible");
var bOverrideScroll = overflow == "scroll" || overflowAxis == "scroll";
return (bShouldScroll && bAllowedScroll) || bOverrideScroll;
};
$.fn.mousedown = function(data, fn) {
if ( fn == null ) {
fn = data;
data = null;
}
var o = fn;
fn = function(e){
if(!inScrollRange(e)) {
return o.apply(this, arguments);
}
return;
};
if ( arguments.length > 0 ) {
return this.bind( "mousedown", data, fn );
}
return this.trigger( "mousedown" );
};
$.fn.mouseup = function(data, fn) {
if ( fn == null ) {
fn = data;
data = null;
}
var o = fn;
fn = function(e){
if(!inScrollRange(e)) {
return o.apply(this, arguments);
}
return;
};
if ( arguments.length > 0 ) {
return this.bind( "mouseup", data, fn );
}
return this.trigger( "mouseup" );
};
$.fn.mousedownScroll = function(data, fn) {
if ( fn == null ) {
fn = data;
data = null;
}
var o = fn;
fn = function(e){
if(inScrollRange(e)) {
e.type = "mousedownscroll";
return o.apply(this, arguments);
}
return;
};
if ( arguments.length > 0 ) {
return this.bind( "mousedown", data, fn );
}
return this.trigger( "mousedown" );
};
$.fn.mouseupScroll = function(data, fn) {
if ( fn == null ) {
fn = data;
data = null;
}
var o = fn;
fn = function(e){
if(inScrollRange(e)) {
e.type = "mouseupscroll";
return o.apply(this, arguments);
}
return;
};
if ( arguments.length > 0 ) {
return this.bind( "mouseup", data, fn );
}
return this.trigger( "mouseup" );
};
var RECT = function(){
this.top = 0;
this.left = 0;
this.bottom = 0;
this.right = 0;
}
function inRect(rect, x, y){
return (y >= rect.top && y <= rect.bottom) &&
(x >= rect.left && x <= rect.right)
}
var scrollSize = measureScrollWidth();
function inScrollRange(event){
var x = event.pageX,
y = event.pageY,
e = $(event.target),
hasY = e.hasScroll(),
hasX = e.hasScroll("x"),
rX = null,
rY = null,
bInX = false,
bInY = false
if(hasY){
rY = new RECT();
rY.top = e.offset().top;
rY.right = e.offset().left + e.width();
rY.bottom = rY.top +e.height();
rY.left = rY.right - scrollSize;
//if(hasX) rY.bottom -= scrollSize;
bInY = inRect(rY, x, y);
}
if(hasX){
rX = new RECT();
rX.bottom = e.offset().top + e.height();
rX.left = e.offset().left;
rX.top = rX.bottom - scrollSize;
rX.right = rX.left + e.width();
//if(hasY) rX.right -= scrollSize;
bInX = inRect(rX, x, y);
}
return bInX || bInY;
}
$(document).on("mousedown", function(e){
//Determine if has scrollbar(s)
if(inScrollRange(e)){
$(e.target).trigger("mousedownScroll");
}
});
$(document).on("mouseup", function(e){
if(inScrollRange(e)){
$(e.target).trigger("mouseupScroll");
}
});
});
function measureScrollWidth() {
var scrollBarMeasure = $('<div />');
$('body').append(scrollBarMeasure);
scrollBarMeasure.width(50).height(50)
.css({
overflow: 'scroll',
visibility: 'hidden',
position: 'absolute'
});
var scrollBarMeasureContent = $('<div />').height(1);
scrollBarMeasure.append(scrollBarMeasureContent);
var insideWidth = scrollBarMeasureContent.width();
var outsideWitdh = scrollBarMeasure.width();
scrollBarMeasure.remove();
return outsideWitdh - insideWidth;
};
Here's a JSFiddle of what I decided to do instead.
$.fn.hasScroll = function(axis){
var overflow = this.css("overflow"),
overflowAxis,
bShouldScroll,
bAllowedScroll,
bOverrideScroll;
if(typeof axis == "undefined" || axis == "y") overflowAxis = this.css("overflow-y");
else overflowAxis = this.css("overflow-x");
bShouldScroll = this.get(0).scrollHeight > this.innerHeight();
bAllowedScroll = (overflow == "auto" || overflow == "visible") ||
(overflowAxis == "auto" || overflowAxis == "visible");
bOverrideScroll = overflow == "scroll" || overflowAxis == "scroll";
return (bShouldScroll && bAllowedScroll) || bOverrideScroll;
};
$.fn.mousedownScroll = function(fn, data){
var ev_mds = function(e){
if(inScrollRange(e)) fn.call(data, e);
}
$(this).on("mousedown", ev_mds);
return ev_mds;
};
$.fn.mouseupScroll = function(fn, data){
var ev_mus = function(e){
if(inScrollRange(e)) fn.call(data, e);
}
$(this).on("mouseup", ev_mus);
return ev_mus;
};
$.fn.mousedownContent = function(fn, data){
var ev_mdc = function(e){
if(!inScrollRange(e)) fn.call(data, e);
}
$(this).on("mousedown", ev_mdc);
return ev_mdc;
};
$.fn.mouseupContent = function(fn, data){
var ev_muc = function(e){
if(!inScrollRange(e)) fn.call(data, e);
}
$(this).on("mouseup", ev_muc);
return ev_muc;
};
var RECT = function(){
this.top = 0;
this.left = 0;
this.bottom = 0;
this.right = 0;
}
function inRect(rect, x, y){
return (y >= rect.top && y <= rect.bottom) &&
(x >= rect.left && x <= rect.right)
}
var scrollSize = measureScrollWidth();
function inScrollRange(event){
var x = event.pageX,
y = event.pageY,
e = $(event.target),
hasY = e.hasScroll(),
hasX = e.hasScroll("x"),
rX = null,
rY = null,
bInX = false,
bInY = false
if(hasY){
rY = new RECT();
rY.top = e.offset().top;
rY.right = e.offset().left + e.width();
rY.bottom = rY.top +e.height();
rY.left = rY.right - scrollSize;
//if(hasX) rY.bottom -= scrollSize;
bInY = inRect(rY, x, y);
}
if(hasX){
rX = new RECT();
rX.bottom = e.offset().top + e.height();
rX.left = e.offset().left;
rX.top = rX.bottom - scrollSize;
rX.right = rX.left + e.width();
//if(hasY) rX.right -= scrollSize;
bInX = inRect(rX, x, y);
}
return bInX || bInY;
}
function measureScrollWidth() {
var scrollBarMeasure = $('<div />');
$('body').append(scrollBarMeasure);
scrollBarMeasure.width(50).height(50)
.css({
overflow: 'scroll',
visibility: 'hidden',
position: 'absolute'
});
var scrollBarMeasureContent = $('<div />').height(1);
scrollBarMeasure.append(scrollBarMeasureContent);
var insideWidth = scrollBarMeasureContent.width();
var outsideWitdh = scrollBarMeasure.width();
scrollBarMeasure.remove();
return outsideWitdh - insideWidth;
};
The only solution that works for me (only tested against IE11):
$(document).mousedown(function(e){
bScrollbarClicked = e.clientX > document.documentElement.clientWidth || e.clientY > document.documentElement.clientHeight;
});
I needed to detect scrollbar on mousedown but not on window but on div,
and I've had element that fill the content, that I was using to detect size without scrollbar:
.element {
position: relative;
}
.element .fill-node {
position: absolute;
left: 0;
top: -100%;
width: 100%;
height: 100%;
margin: 1px 0 0;
border: none;
opacity: 0;
pointer-events: none;
box-sizing: border-box;
}
the code for detect was similar to #DariuszSikorski answer but including offset and using the node that was inside scrollable:
function scrollbar_event(e, node) {
var left = node.offset().left;
return node.outerWidth() <= e.clientX - left;
}
var node = self.find('.fill-node');
self.on('mousedown', function(e) {
if (!scrollbar_event(e, node)) {
// click on content
}
});
Tested and working in chrome and firefox in ubuntu 21.10.
const isScrollClick =
e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight;
clickOnScrollbar = event.clientX > event.target.clientWidth || event.clientY > event.target.clientHeight;
tested on Chrome / Mac OS

Using Google charts API with MVC 3 Razor and jquery ajax

I'm trying to display a pie chart in my website using Google charts API so far i cant get it to work and I couldn't find any examples that use MVC 3 Razor.
here is my code im using json to get the data
// Load the Visualization API and the piechart package.
google.load('visualization', '1.0', { 'packages': ['corechart'] });
// Set a callback to run when the Google Visualization API is loaded.
google.setOnLoadCallback(drawChart);
JSON.stringify = JSON.stringify || function (obj) {
var t = typeof (obj);
if (t != "object" || obj === null) {
// simple data type
if (t == "string") obj = '"' + obj + '"';
return String(obj);
}
else {
// recurse array or object
var n, v, json = [], arr = (obj && obj.constructor == Array);
for (n in obj) {
v = obj[n]; t = typeof (v);
if (t == "string") v = '"' + v + '"';
else if (t == "object" && v !== null) v = JSON.stringify(v);
json.push((arr ? "" : '"' + n + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
};
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart() {
// Create the data table.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Topping');
data.addColumn('number', 'Slices');
$.post('#Url.Content("~/Home/GetMyChart1")',
function (items) {
// Successful requests get here
alert(JSON.stringify(items) + " - " + items.rows.length);
data.addRows(items.rows.length);
$.each(items.rows, function (i, item) {
alert(i);
data.setCell(i, 0, item.Name);
data.setCell(i, 1, item.ID);
});
alert("finished");
alert(data.length);
});
// Set chart options
var options = { 'title': 'How Much Pizza I Ate Last Night',
'width': 400,
'height': 300
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.PieChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
The controller Code
public ActionResult GetMyChart1(string CurrentClass)
{
var tests = from t in db.Tests
group t by new { t.StudentID, t.Student.CurrentSchoolGrade } into tl
select new { StudentID = tl.Key.StudentID, Class = tl.Key.CurrentSchoolGrade, Score = (tl.Sum(k => k.Score)/tl.Sum(l => l.Part.Score))* 100, Count = tl.Count() };
var results = from t in tests
where t.Class == CurrentClass
select t;
List<DataItem> dt = new List<DataItem>();
dt.Add(new DataItem(results.Count(x => x.Score <= 40), "0% - 40%"));
dt.Add(new DataItem(results.Count(x => x.Score <= 60 && x.Score > 40), "40% - 60%"));
dt.Add(new DataItem(results.Count(x => x.Score <= 80 && x.Score > 60), "60% - 80%"));
dt.Add(new DataItem(results.Count(x => x.Score <= 100 && x.Score > 60), "80% - 100%"));
chartJson cj = new chartJson();
cj.rows = dt;
return Json(cj);
}
public class chartJson
{
public List<DataItem> rows { get; set; }
}
public class DataItem
{
public DataItem(int id, string name)
{
ID = id;
Name = name;
}
public int ID { get; set; }
public string Name { get; set; }
}
all the alerts returns correct values except alert(data.length); it returns undefined
and the drawing div appears with a label written in it No data
I am thinking that you need to move the chart drawing lines inside of the POST callback:
$.post('#Url.Content("~/Home/GetMyChart1")', function (items) {
// Successful requests get here
alert(JSON.stringify(items) + " - " + items.rows.length);
data.addRows(items.rows.length);
$.each(items.rows, function (i, item) {
alert(i);
data.setCell(i, 0, item.Name);
data.setCell(i, 1, item.ID);
});
alert("finished");
alert(data.length);
// Set chart options
var options = {
'title': 'How Much Pizza I Ate Last Night',
'width': 400,
'height': 300
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.PieChart(document.getElementById('chart_div'));
chart.draw(data, options);
});
};
Google charts API is written in JavaScript so it can be used with any web framework, including ASP.NET MVC. All that you need to do is to include it in your views. It shouldn't be limited or not work because you are using ASP.NET MVC.
After reviewing the full sample of code it looks like the google.setOnLoadCallback(drawChart); is most likely executing the drawChart method before the data is ready. Thus no chart.
Instead of making an Ajax .Post to the server to retrieve data, just build your data on the initial request.

How to add toggle button from jqgrid toolbar to autogenerated context menu

Toggle button in jqgrid top toolbar is defined using Oleg answer as
var autoedit;
$("#grid_toppager_left table.navtable tbody tr").append(
'<td class="ui-pg-button ui-corner-all" >' +
'<div class="ui-pg-div my-nav-checkbox">' +
'<input tabindex="-1" type="checkbox" id="AutoEdit" '+(autoedit ? 'checked ' : '')+'/>' +
'<label title="Toggle autoedit" for="AutoEdit">this text is ignored in toolbar</label></div></td>'
);
$("#AutoEdit").button({
text: false,
icons: {primary: "ui-icon-star"}
}).click(function () {
autoedit = $(this).is(':checked');
});
Answer from how to add standard textbox command to jqgrid context menu is used to autogenerate context menu for grid from toolbar.
In generated context menu for this item only text "this text is ignored in toolbar" appears and selecting it does nothing.
How to make it work or remove this item from context menu?
Look at the demo or the same demo with another themes: this and this.
First of all I modified the code of the jquery.contextmenu.js to support jQuery UI Themes. Then I modified the code more, to be able to create context menu more dynamically. In the modified version of jquery.contextmenu.js one can crate menu and the bindings not only in the onContextMenu, but also in onShowMenu. Inside of onContextMenu one can create just the empty menu
<div id="myMenu"><ul></ul></div>
It is important only if one use dynamically switching of the icons of the text of buttons from the navigator bar.
You can download the modified version of the file here.
In the demo I used the last modification of the code from the answer, so the standard context menu can be still used in the grid on selected text or in the enabled input/textarea fields. The context menu of the browser will be displayed in the case:
I modified the code of createContexMenuFromNavigatorButtons function from the answer to the following:
var getSelectedText = function () {
var text = '';
if (window.getSelection) {
text = window.getSelection();
} else if (document.getSelection) {
text = document.getSelection();
} else if (document.selection) {
text = document.selection.createRange().text;
}
return typeof (text) === 'string' ? text : text.toString();
},
createContexMenuFromNavigatorButtons = function (grid, pager) {
var menuId = 'menu_' + grid[0].id, menuUl = $('<ul>'),
menuDiv = $('<div>').attr('id', menuId);
menuUl.appendTo(menuDiv);
menuDiv.appendTo('body');
grid.contextMenu(menuId, {
bindings: {}, // the bindings will be created in the onShowMenu
onContextMenu: function (e) {
var p = grid[0].p, i, lastSelId, $target = $(e.target),
rowId = $target.closest("tr.jqgrow").attr("id"),
isInput = $target.is(':text:enabled') ||
$target.is('input[type=textarea]:enabled') ||
$target.is('textarea:enabled');
if (rowId && !isInput && getSelectedText() === '') {
i = $.inArray(rowId, p.selarrrow);
if (p.selrow !== rowId && i < 0) {
// prevent the row from be unselected
// the implementation is for "multiselect:false" which we use,
// but one can easy modify the code for "multiselect:true"
grid.jqGrid('setSelection', rowId);
} else if (p.multiselect) {
// Edit will edit FIRST selected row.
// rowId is allready selected, but can be not the last selected.
// Se we swap rowId with the first element of the array p.selarrrow
lastSelId = p.selarrrow[p.selarrrow.length - 1];
if (i !== p.selarrrow.length - 1) {
p.selarrrow[p.selarrrow.length - 1] = rowId;
p.selarrrow[i] = lastSelId;
p.selrow = rowId;
}
}
return true;
} else {
return false; // no contex menu
}
},
onShowMenu: function (e, $menu) {
var options = this, $menuUl = $menu.find('ul:first').empty();
$('table.navtable .ui-pg-button', pager).each(function () {
var $spanIcon, text, $td, id, $li, $a, button,
$div = $(this).children('div.ui-pg-div:first'),
gridId = grid[0].id;
if ($div.length === 1) {
text = $div.text();
$td = $div.parent();
if (text === '') {
text = $td.attr('title');
}
if (this.id !== '' && text !== '') {
id = 'menuitem_' + this.id;
if (id.length > gridId.length + 2) {
id = id.substr(0, id.length - gridId.length - 1);
}
} else {
// for custom buttons
id = $.jgrid.randId();
}
$li = $('<li>').attr('id', id);
$spanIcon = $div.children('span.ui-icon');
if ($spanIcon.length > 0) {
// standard navGrid button or button added by navButtonAdd
$li.append($('<a>')
.text(text)
.prepend($spanIcon.clone().css({
float: 'left',
marginRight: '0.5em'
})));
$menuUl.append($li);
options.bindings[id] = (function ($button) {
return function () { $button.click(); };
}($div));
} else {
button = $div.children("input").data("button");
if (button !== undefined) {
$a = $('<a>')
.text(button.options.label)
.prepend(
$('<label>').addClass("ui-corner-all").css({
float: 'left',
width: '16px',
borderWidth: '0px',
marginRight: '0.5em'//'4px'
}).append(
$('<span>').addClass("ui-button-icon-primary ui-icon " +
button.options.icons.primary)
.css({
float: 'left',
marginRight: '0.5em'
})
)
);
$li.append($a);
if (button.type === "checkbox" && button.element.is(':checked')) {
$a.find('label:first').addClass("ui-state-active");
}
$menuUl.append($li);
options.bindings[id] = (function ($button, isCheckbox) {
if (isCheckbox) {
return function () {
if ($button.is(':checked')) {
$button.siblings('label').removeClass("ui-state-active");
} else {
$button.siblings('label').addClass("ui-state-active");
}
$button.click();
$button.button("refresh"); // needed for IE7-IE8
};
} else {
return function () { $button.click(); };
}
}(button.element, button.type === "checkbox"));
}
}
}
});
return $menu;
}
});
},
autoedit = false;
and fill the check-button in the navigator bar with the code which is changed only a little:
$("#pager_left table.navtable tbody tr").append(
'<td class="ui-pg-button ui-corner-all">' +
'<div class="ui-pg-div my-nav-checkbox">' +
'<input tabindex="-1" type="checkbox" id="AutoEdit" />' +
'<label title="Checkx caption which should appear as button tooltip"' +
' for="AutoEdit">Autoedit</label></div></td>'
);
$("#AutoEdit").button({
text: false,
icons: {primary: "ui-icon-mail-closed"}
}).click(function () {
var iconClass, $this = $(this);
if (!autoedit) { // $this.is(':checked')) {
autoedit = true;
iconClass = "ui-icon-mail-open";
} else {
autoedit = false;
iconClass = "ui-icon-mail-closed";
}
$this.button("option", {icons: {primary: iconClass}});
});
createContexMenuFromNavigatorButtons($grid, '#pager');
UPDATED: One more demo which support buttons added by new inlineNav method you can find here. Additionally I included in the demo the function normalizePagers which I use to improve the look of the pager:
How you can see the contextmenu includes only enabled buttons from the navigator bar.

JQgrid checkbox onclick update database

I have a checkbox column in my JqGrid which get loaded from DB, so it is either checked or not checked when it is loaded.
What i want is : If checkbox is being checked or uncheked by user i want to update DB in at same. I dont want user to press enter or anything. only 1 click and send action to DB
name: 'Aktiv', index: 'Aktiv', width: 100, edittype: 'checkbox', align: 'center',formatter: "checkbox", editable: true, formatoptions: {disabled : false}
You can set a click event handler inside of loadComplete:
loadComplete: function () {
var iCol = getColumnIndexByName ($(this), 'Aktiv'), rows = this.rows, i,
c = rows.length;
for (i = 1; i < c; i += 1) {
$(rows[i].cells[iCol]).click(function (e) {
var id = $(e.target).closest('tr')[0].id,
isChecked = $(e.target).is(':checked');
alert('clicked on the checkbox in the row with id=' + id +
'\nNow the checkbox is ' +
(isChecked? 'checked': 'not checked'));
});
}
}
where
var getColumnIndexByName = function(grid, columnName) {
var cm = grid.jqGrid('getGridParam', 'colModel'), i, l;
for (i = 1, l = cm.length; i < l; i += 1) {
if (cm[i].name === columnName) {
return i; // return the index
}
}
return -1;
};
Instead of the alert you should use jQuery.ajax to send information to the server about updating the checkbox state.
You can see a demo here.
A small correction in the loadComplete: function().
in the demo you can find that even after the checkbox is checked, if you click outside the checkbox in that cell, the value gets changed to 'false' from 'true'.
To avoid this, just give the focus exactly on the checkbox alone by doing the following.
for (i = 1; i < c; i += 1) {
$(('input[type="checkbox"]'),rows[i].cells[iCol]).click(function (e) {
var id = $(e.target).closest('tr')[0].id,
isChecked = $(e.target).is(':checked');
alert('clicked on the checkbox in the row with id=' + id +
'\nNow the checkbox is ' +
(isChecked? 'checked': 'not checked'));
});
}
and thanks for the answer :-) (#Oleg) helped me a lot..in time of course.. ;)
To change values of other column based on click of checkbox
var weightedAvgPriceIndex = getColumnIndexByName($(this), 'WeightedAveragePrice'),
rows = this.rows,
i,
c = rows.length;
for (i = 1; i < c; i += 1) {
$(('input[type="checkbox"]'),rows[i].cells[iCol]).click(function (e) {
var id = $(e.target).closest('tr')[0].id;
isChecked = $(e.target).is(':checked');
var x = $('#' + id + ' td:eq(' + weightedAvgPriceIndex + ')').text();
$('#' + id + ' td:eq(' + weightedAvgPriceIndex + ')').text(Math.abs(x) + 10);
});
}

Resources