diff --git a/.eslintrc b/.eslintrc
index 053432d7..8ddfb221 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,10 +1,10 @@
{
"extends": "eslint:recommended",
"env": {
- "browser": true
+ "browser": true,
+ "es2015": true
},
"globals": {
- "jQuery": true,
"Slick": true
},
"rules": {
diff --git a/slick.core.js b/slick.core.js
index a9313fe3..c5f3422f 100644
--- a/slick.core.js
+++ b/slick.core.js
@@ -12,15 +12,26 @@
* @constructor
*/
function EventData(event, args) {
-
- var nativeEvent = event;
- var arguments_ = args;
- var isPropagationStopped = false;
- var isImmediatePropagationStopped = false;
- var isDefaultPrevented = false;
- var returnValues = [];
- var returnValue = undefined;
-
+ this.event = event;
+ let nativeEvent = event;
+ let arguments_ = args;
+ let isPropagationStopped = false;
+ let isImmediatePropagationStopped = false;
+ let isDefaultPrevented = false;
+ let returnValues = [];
+ let returnValue = undefined;
+
+ // when we already have an event, we want to keep some of the event properties
+ if (event) {
+ const eventProps = [
+ 'altKey', 'ctrlKey', 'metaKey', 'shiftKey', 'key', 'keyCode',
+ 'clientX', 'clientY', 'offsetX', 'offsetY', 'pageX', 'pageY',
+ 'bubbles', 'type', 'which', 'x', 'y'
+ ];
+ for (let key of eventProps) {
+ this[key] = event[key];
+ }
+ }
this.target = nativeEvent ? nativeEvent.target : undefined;
/***
@@ -143,9 +154,9 @@
* If not specified, the scope will be set to the Event
instance.
*/
this.notify = function (args, e, scope) {
-
- if(!(e instanceof EventData))
+ if (!(e instanceof EventData)) {
e = new EventData(e, args);
+ }
scope = scope || this;
for (var i = 0; i < handlers.length && !(e.isPropagationStopped() || e.isImmediatePropagationStopped()); i++) {
@@ -748,14 +759,14 @@
}
function offset(el) {
+ const box = el.getBoundingClientRect();
+ const docElem = document.documentElement;
- box = el.getBoundingClientRect();
- docElem = document.documentElement;
return {
top: box.top + window.pageYOffset - docElem.clientTop,
left: box.left + window.pageXOffset - docElem.clientLeft
};
- }
+ }
function width(el, value) {
if (value === undefined) {
@@ -905,7 +916,7 @@
function isFunction( obj ) {
return typeof obj === "function" && typeof obj.nodeType !== "number" &&
typeof obj.item !== "function";
- };
+ }
function isPlainObject( obj ) {
var proto, Ctor;
if ( !obj || toString.call( obj ) !== "[object Object]" ) {
@@ -924,7 +935,8 @@
target = arguments[ 0 ],
i = 1,
length = arguments.length,
- deep = true;
+ deep = false;
+
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[ i ] || {};
@@ -967,9 +979,52 @@
}
}
return target;
- };
+ }
+
+ /**
+ * A simple binding event service to keep track of all events being subscribed to,
+ * it allows us to unbind event(s) and their listener(s) by calling a simple unbind method call.
+ * Unbinding is a necessary step to make sure that all event listeners are removed to avoid memory leaks when destroing the grid
+ */
+ function BindingEventService() {
+ let _boundedEvents = [];
+
+ this.destroy = function () {
+ this.unbindAll();
+ _boundedEvents = [];
+ }
- // exports
+ /** Bind an event listener to any element */
+ this.bind = function (element, eventName, listener, options) {
+ element.addEventListener(eventName, listener, options);
+ _boundedEvents.push({ element: element, eventName, listener });
+ }
+
+ /** Unbind all will remove every every event handlers that were bounded earlier */
+ this.unbind = function (element, eventName, listener) {
+ if (element && element.removeEventListener) {
+ element.removeEventListener(eventName, listener);
+ }
+ }
+
+ this.unbindByName = function (element, eventName) {
+ const boundedEvent = _boundedEvents.find(e => e.element === element && e.eventName === eventName);
+ if (boundedEvent) {
+ this.unbind(boundedEvent.element, boundedEvent.eventName, boundedEvent.listener);
+ }
+ }
+
+ /** Unbind all will remove every every event handlers that were bounded earlier */
+ this.unbindAll = function () {
+ while (_boundedEvents.length > 0) {
+ const boundedEvent = _boundedEvents.pop();
+ const { element, eventName, listener } = boundedEvent;
+ this.unbind(element, eventName, listener);
+ }
+ }
+ }
+
+ // export Slick namespace on both global & window objects
window.Slick = {
"Event": Event,
"EventData": EventData,
@@ -981,6 +1036,7 @@
"GroupTotals": GroupTotals,
"RegexSanitizer": regexSanitizer,
"EditorLock": EditorLock,
+ "BindingEventService": BindingEventService,
"Utils":
{
"extend": extend,
@@ -1019,7 +1075,7 @@
}
return ret;
}
- }
+ }
},
/***
* A global singleton editor lock.
@@ -1088,4 +1144,11 @@
HTML: 'HTML'
}
}
+
+ /* eslint-disable no-undef */
+ // also add to global object when exist
+ if (typeof global !== "undefined") {
+ global.Slick = window.Slick;
+ }
+ /* eslint-enable no-undef */
})(window);
diff --git a/slick.dataview.js b/slick.dataview.js
index d6dcc959..02c58841 100644
--- a/slick.dataview.js
+++ b/slick.dataview.js
@@ -78,7 +78,7 @@
var onGroupExpanded = new Slick.Event();
var onGroupCollapsed = new Slick.Event();
- options = Slick.Utils.extend({}, defaults, options);
+ options = Slick.Utils.extend(true, {}, defaults, options);
/***
* Begins a bached update of the items in the data view.
@@ -330,7 +330,7 @@
groupingInfos = (groupingInfo instanceof Array) ? groupingInfo : [groupingInfo];
for (var i = 0; i < groupingInfos.length; i++) {
- var gi = groupingInfos[i] = Slick.Utils.extend({}, groupingInfoDefaults, groupingInfos[i]);
+ var gi = groupingInfos[i] = Slick.Utils.extend(true, {}, groupingInfoDefaults, groupingInfos[i]);
gi.getterIsAFn = typeof gi.getter === "function";
// pre-compile accumulator loops
@@ -1206,7 +1206,7 @@
return;
}
- var previousPagingInfo = Slick.Utils.extend({}, getPagingInfo());
+ var previousPagingInfo = Slick.Utils.extend(true, {}, getPagingInfo());
var countBefore = rows.length;
var totalRowsBefore = totalRows;
@@ -1331,7 +1331,7 @@
if (args.added) {
if (preserveHiddenOnSelectionChange && grid.getOptions().multiSelect) {
// find the ones that are hidden
- var hiddenSelectedRowIds = $.grep(selectedRowIds, function (id) {
+ var hiddenSelectedRowIds = Slick.Utils.grep(selectedRowIds, function (id) {
return self.getRowById(id) === undefined;
});
// add the newly selected ones
@@ -1342,7 +1342,7 @@
} else {
if (preserveHiddenOnSelectionChange && grid.getOptions().multiSelect) {
// remove rows whose id is on the list
- rowIds = $.grep(selectedRowIds, function (id) {
+ rowIds = Slick.Utils.grep(selectedRowIds, function (id) {
return args.ids.indexOf(id) === -1;
});
} else {
@@ -1685,7 +1685,7 @@
// TODO: merge common aggregators in one to prevent needles iterating
// exports
- Slick.Utils.extend(Slick, {
+ Slick.Utils.extend(true, Slick, {
Data: {
DataView: DataView,
Aggregators: {
diff --git a/slick.grid.js b/slick.grid.js
index 6ece839f..19bdf3dc 100644
--- a/slick.grid.js
+++ b/slick.grid.js
@@ -161,6 +161,7 @@ if (typeof Slick === "undefined") {
const show = utils.show;
const hide = utils.hide;
+ var _bindingEventService = new Slick.BindingEventService();
var initialized = false;
var _container;
var uid = "slickgrid_" + Math.round(1000000 * Math.random());
@@ -327,7 +328,7 @@ if (typeof Slick === "undefined") {
// calculate these only once and share between grid instances
maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight();
- options = utils.extend({}, defaults, options);
+ options = utils.extend(true, {}, defaults, options);
validateAndEnforceOptions();
columnDefaults.width = options.defaultColumnWidth;
@@ -549,7 +550,7 @@ if (typeof Slick === "undefined") {
// disable text selection in grid cells except in input and textarea elements
// (this is IE-specific, because selectstart event will only fire in IE)
_viewport.forEach(function (view) {
- view.addEventListener("selectstart.ui", function (event) {
+ _bindingEventService.bind(view, "selectstart.ui", function (event) {
if(event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement)
{
return;
@@ -572,9 +573,9 @@ if (typeof Slick === "undefined") {
resizeCanvas();
bindAncestorScrollEvents();
- _container.addEventListener("resize.slickgrid", resizeCanvas);
+ _bindingEventService.bind(_container, "resize.slickgrid", resizeCanvas);
_viewport.forEach(function (view) {
- view.addEventListener("scroll", handleScroll);
+ _bindingEventService.bind(view, "scroll", handleScroll);
});
if (options.enableMouseWheelScrollHandler) {
@@ -587,39 +588,39 @@ if (typeof Slick === "undefined") {
}
_headerScroller.forEach(function (el) {
- el.addEventListener("contextmenu", handleHeaderContextMenu);
- el.addEventListener("click", handleHeaderClick);
+ _bindingEventService.bind(el, "contextmenu", handleHeaderContextMenu);
+ _bindingEventService.bind(el, "click", handleHeaderClick);
});
_headerRowScroller.forEach(function (scroller) {
- scroller.addEventListener("scroll", handleHeaderRowScroll);
+ _bindingEventService.bind(scroller, "scroll", handleHeaderRowScroll);
});
if (options.createFooterRow) {
_footerRow.forEach(function (footer) {
- footer.addEventListener("contextmenu", handleFooterContextMenu)
- footer.addEventListener("click", handleFooterClick);
+ _bindingEventService.bind(footer, "contextmenu", handleFooterContextMenu)
+ _bindingEventService.bind(footer, "click", handleFooterClick);
});
_footerRowScroller.forEach(function (scroller) {
- scroller.addEventListener("scroll", handleFooterRowScroll);
+ _bindingEventService.bind(scroller, "scroll", handleFooterRowScroll);
});
}
if (options.createPreHeaderPanel) {
- _preHeaderPanelScroller.addEventListener("scroll", handlePreHeaderPanelScroll);
+ _bindingEventService.bind(_preHeaderPanelScroller, "scroll", handlePreHeaderPanelScroll);
}
- _focusSink.addEventListener("keydown", handleKeyDown);
- _focusSink2.addEventListener("keydown", handleKeyDown);
+ _bindingEventService.bind(_focusSink, "keydown", handleKeyDown);
+ _bindingEventService.bind(_focusSink2, "keydown", handleKeyDown);
_canvas.forEach(function (element) {
- element.addEventListener("keydown", handleKeyDown);
- element.addEventListener("click", handleClick);
- element.addEventListener("dblclick", handleDblClick);
- element.addEventListener("contextmenu", handleContextMenu);
- element.addEventListener("mouseover", handleCellMouseOver);
- element.addEventListener("mouseout", handleCellMouseOut);
+ _bindingEventService.bind(element, "keydown", handleKeyDown);
+ _bindingEventService.bind(element, "click", handleClick);
+ _bindingEventService.bind(element, "dblclick", handleDblClick);
+ _bindingEventService.bind(element, "contextmenu", handleContextMenu);
+ _bindingEventService.bind(element, "mouseover", handleCellMouseOver);
+ _bindingEventService.bind(element, "mouseout", handleCellMouseOut);
});
if (Slick.Draggable) {
@@ -982,7 +983,7 @@ if (typeof Slick === "undefined") {
target.forEach(function (el) {
el.setAttribute("unselectable", "on")
el.style.MozUserSelect = "none";
- el.addEventListener("selectstart.ui", function () {
+ _bindingEventService.bind(el, "selectstart.ui", function () {
return false;
});
});
@@ -1041,14 +1042,14 @@ if (typeof Slick === "undefined") {
// bind to scroll containers only
if (elem == _viewportTopL || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) {
_boundAncestors.push(elem);
- elem.addEventListener("scroll." + uid, handleActiveCellPositionChange);
+ _bindingEventService.bind(elem, "scroll." + uid, handleActiveCellPositionChange);
}
}
}
function unbindAncestorScrollEvents() {
_boundAncestors.forEach(function (ancestor) {
- ancestor.removeEventListener("scroll." + uid);
+ _bindingEventService.unbindByName(ancestor, "scroll." + uid);
});
_boundAncestors = [];
}
@@ -1336,14 +1337,14 @@ if (typeof Slick === "undefined") {
if(classname)
header.classList.add(classname);
- header.addEventListener("mouseenter", handleHeaderMouseEnter);
- header.addEventListener("mouseleave", handleHeaderMouseLeave);
+ _bindingEventService.bind(header, "mouseenter", handleHeaderMouseEnter);
+ _bindingEventService.bind(header, "mouseleave", handleHeaderMouseLeave);
utils.storage.put(header, "column", m);
if (options.enableColumnReorder || m.sortable) {
- header.addEventListener("mouseenter", handleHeaderMouseHoverOn);
- header.addEventListener("mouseleave", handleHeaderMouseHoverOff);
+ _bindingEventService.bind(header, "mouseenter", handleHeaderMouseHoverOn);
+ _bindingEventService.bind(header, "mouseleave", handleHeaderMouseHoverOff);
}
if(m.hasOwnProperty('headerCellAttrs') && m.headerCellAttrs instanceof Object) {
@@ -1374,8 +1375,8 @@ if (typeof Slick === "undefined") {
if(classname)
headerRowCell.classList.add(classname);
- headerRowCell.addEventListener("mouseenter", handleHeaderRowMouseEnter);
- headerRowCell.addEventListener("mouseleave", handleHeaderRowMouseLeave);
+ _bindingEventService.bind(headerRowCell, "mouseenter", handleHeaderRowMouseEnter);
+ _bindingEventService.bind(headerRowCell, "mouseleave", handleHeaderRowMouseLeave);
utils.storage.put(headerRowCell, "column", m);
@@ -1413,7 +1414,7 @@ if (typeof Slick === "undefined") {
_headers.forEach(function (header) {
- header.addEventListener("click", function (e) {
+ _bindingEventService.bind(header, "click", function (e) {
if (columnResizeDragging)
return;
@@ -1687,7 +1688,7 @@ if (typeof Slick === "undefined") {
function handleResizeableHandleDoubleClick(evt) {
const triggeredByColumn = evt.target.parentElement.id.replace(uid, "");
- trigger(self.onColumnsResizeDblClick, { triggeredByColumn: triggeredByColumn.getReturnValue() });
+ trigger(self.onColumnsResizeDblClick, { triggeredByColumn: triggeredByColumn });
}
function setupColumnResize() {
@@ -1725,11 +1726,11 @@ if (typeof Slick === "undefined") {
if (i >= columns.length) { return; }
if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) {
- return;
+ continue;
}
const resizeableHandle = utils.template("