Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| HTMLWidgets.widget({ | |
| name: 'rhandsontable', | |
| type: 'output', | |
| params: null, | |
| initialize: function(el, width, height) { | |
| return { | |
| }; | |
| }, | |
| renderValue: function(el, x, instance) { | |
| // convert json to array | |
| if (x.data.length > 0 && x.data[0].constructor === Array) { | |
| x.data = x.data; | |
| } else { | |
| x.data = toArray(x.data.map(function(d) { | |
| return x.rColnames.map(function(ky) { | |
| return d[ky]; | |
| }); | |
| })); | |
| } | |
| if (x.isHeatmap === true) { | |
| x.afterLoadData = this.initHeatmap; | |
| x.beforeChangeRender = this.updateHeatmap; | |
| } | |
| if (x.overflow) { | |
| $("#" + el.id).css('overflow', x.overflow); | |
| } | |
| if (x.rowHeaderWidth) { | |
| $("#" + el.id).css('col.rowHeader', x.rowHeaderWidth + 'px'); | |
| } | |
| //this.afterRender(x); | |
| this.params = x; | |
| if (instance.hot) { // update existing instance | |
| if (x.debug && x.debug > 0) { | |
| console.log("rhandsontable: update table"); | |
| } | |
| instance.hot.params = x; | |
| instance.hot.updateSettings(x); | |
| } else { // create new instance | |
| if (x.debug && x.debug > 0) { | |
| console.log("rhandsontable: new table"); | |
| } | |
| instance.hot = new Handsontable(el, x); | |
| this.afterChangeCallback(x); | |
| this.afterCellMetaCallback(x); | |
| this.afterRowAndColChange(x); | |
| if (x.selectCallback) { | |
| this.afterSelectCallback(x); | |
| } | |
| instance.hot.params = x; | |
| instance.hot.updateSettings(x); | |
| var searchField = document.getElementById('searchField'); | |
| if (typeof(searchField) != 'undefined' && searchField !== null) { | |
| Handsontable.dom.addEvent(searchField, 'keyup', function (event) { | |
| var search = instance.hot.getPlugin('search'); | |
| var queryResult = search.query(this.value); | |
| instance.hot.render(); | |
| }); | |
| } | |
| } | |
| }, | |
| resize: function(el, width, height, instance) { | |
| }, | |
| afterRender: function(x) { | |
| x.afterRender = function(isForced) { | |
| var plugin = this.getPlugin('autoColumnSize'); | |
| if (plugin.isEnabled() && this.params) { | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("rhandsontable: resizing column widths"); | |
| } | |
| } | |
| var wdths = plugin.widths; | |
| for(var i = 0, colCount = this.countCols(); i < colCount ; i++) { | |
| if (this.params.columns && this.params.columns[i].renderer.name != "customRenderer") { | |
| plugin.calculateColumnsWidth(i, 300, true); | |
| } | |
| } | |
| } | |
| }; | |
| }, | |
| afterChangeCallback: function(x) { | |
| x.afterChange = function(changes, source) { | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterChange: " + source); | |
| } | |
| if (this.params.debug > 1) { | |
| console.log("afterChange:"); | |
| console.log(changes); | |
| } | |
| } | |
| if (HTMLWidgets.shinyMode) { | |
| if (changes && (changes[0][2] !== null || changes[0][3] !== null)) { | |
| if (this.sortIndex && this.sortIndex.length !== 0) { | |
| c = [this.sortIndex[changes[0][0]][0], changes[0].slice(1, 1 + 3)]; | |
| } else { | |
| c = changes; | |
| } | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterChange: Shiny.onInputChange: " + this.rootElement.id); | |
| } | |
| } | |
| Shiny.onInputChange(this.rootElement.id, { | |
| data: this.getData(), | |
| changes: { event: "afterChange", changes: c, source: source }, | |
| params: this.params | |
| }); | |
| } else if (source == "loadData" && this.params) { | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterChange: Shiny.onInputChange: " + this.rootElement.id); | |
| } | |
| } | |
| // push input change to shiny so input$hot and output$hot are in sync (see #137) | |
| Shiny.onInputChange(this.rootElement.id, { | |
| data: this.getData(), | |
| changes: { event: "afterChange", changes: null }, | |
| params: this.params | |
| }); | |
| } | |
| } | |
| }; | |
| x.afterLoadData = function(firstTime) { | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterLoadData: " + firstTime); | |
| } | |
| } | |
| }; | |
| x.afterChangesObserved = function(firstTime) { | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterChangesObserved"); | |
| } | |
| } | |
| }; | |
| x.afterInit = function() { | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterInit"); | |
| } | |
| } | |
| }; | |
| }, | |
| afterCellMetaCallback: function(x) { | |
| x.afterSetCellMeta = function(r, c, key, val) { | |
| if (HTMLWidgets.shinyMode && key === "comment") { | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterSetCellMeta: Shiny.onInputChange: " + this.rootElement.id); | |
| } | |
| } | |
| Shiny.onInputChange(this.rootElement.id + "_comment", { | |
| data: this.getData(), | |
| comment: { r: r + 1, c: c + 1, key: key, val: val}, | |
| params: this.params | |
| }); | |
| } | |
| }; | |
| }, | |
| afterSelectCallback: function(x) { | |
| x.afterSelectionEnd = function(r, c, r2, c2) { | |
| var r_all = []; | |
| var c_all = []; | |
| if (HTMLWidgets.shinyMode) { | |
| if ( r2 < r ) { r2 = [r, r = r2][0]; } | |
| for ( var i=r; i <= r2; i++ ) { | |
| r_all.push( this.toPhysicalRow(i) + 1 ); | |
| } | |
| if ( c2 < c ) { c2 = [c, c = c2][0]; } | |
| for ( var ii=c; ii <= c2; ii++ ) { | |
| c_all.push( this.toPhysicalColumn(ii) + 1 ); | |
| } | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterSelectionEnd: Shiny.onInputChange: " + this.rootElement.id); | |
| } | |
| } | |
| Shiny.onInputChange(this.rootElement.id + "_select:rhandsontable.customSelectDeserializer", { | |
| data: this.getData(), | |
| select: { r: r + 1, | |
| c: c + 1, | |
| r2: r2 + 1, | |
| c2: c2 + 1, | |
| rAll: r_all, | |
| cAll: c_all }, | |
| params: this.params | |
| }); | |
| } | |
| }; | |
| }, | |
| afterRowAndColChange: function(x) { | |
| x.afterCreateRow = function(ind, ct) { | |
| if (HTMLWidgets.shinyMode) { | |
| if (this.params && this.params.columns) { | |
| for(var i = 0, colCount = this.countCols(); i < colCount ; i++) { | |
| this.setDataAtCell(ind, i, this.params.columns[i].default); | |
| } | |
| } | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterCreateRow: Shiny.onInputChange: " + this.rootElement.id); | |
| } | |
| } | |
| Shiny.onInputChange(this.rootElement.id, { | |
| data: this.getData(), | |
| changes: { event: "afterCreateRow", ind: ind, ct: ct }, | |
| params: this.params | |
| }); | |
| } | |
| }; | |
| x.afterRemoveRow = function(ind, ct) { | |
| if (HTMLWidgets.shinyMode) { | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterRemoveRow: Shiny.onInputChange: " + this.rootElement.id); | |
| } | |
| } | |
| Shiny.onInputChange(this.rootElement.id, { | |
| data: this.getData(), | |
| changes: { event: "afterRemoveRow", ind: ind, ct: ct }, | |
| params: this.params | |
| }); | |
| } | |
| }; | |
| x.afterCreateCol = function(ind, ct) { | |
| if (HTMLWidgets.shinyMode) { | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterCreateCol: Shiny.onInputChange: " + this.rootElement.id); | |
| } | |
| } | |
| Shiny.onInputChange(this.rootElement.id, { | |
| data: this.getData(), | |
| changes: { event: "afterCreateCol", ind: ind, ct: ct }, | |
| params: this.params | |
| }); | |
| } | |
| }; | |
| x.afterRemoveCol = function(ind, ct) { | |
| if (HTMLWidgets.shinyMode) | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterRemoveCol: Shiny.onInputChange: " + this.rootElement.id); | |
| } | |
| } | |
| Shiny.onInputChange(this.rootElement.id, { | |
| data: this.getData(), | |
| changes: { event: "afterRemoveCol", ind: ind, ct: ct }, | |
| params: this.params | |
| }); | |
| }; | |
| x.afterRowMove = function(ind, ct) { | |
| if (HTMLWidgets.shinyMode) | |
| if (this.params && this.params.debug) { | |
| if (this.params.debug > 0) { | |
| console.log("afterRowMove: Shiny.onInputChange: " + this.rootElement.id); | |
| } | |
| } | |
| Shiny.onInputChange(this.rootElement.id, { | |
| data: this.getData(), | |
| changes: { event: "afterRowMove", ind: ind, ct: ct }, | |
| params: this.params | |
| }); | |
| }; | |
| }, | |
| // see http://handsontable.com/demo/heatmaps.html | |
| initHeatmap: function(firstTime, source) { | |
| this.heatmap = []; | |
| for(var i = 0, colCount = this.countCols(); i < colCount ; i++) { | |
| this.heatmap[i] = generateHeatmapData.call(this, i); | |
| } | |
| }, | |
| updateHeatmap: function(change, source) { | |
| this.heatmap[change[0][1]] = generateHeatmapData.call(this, change[0][1]); | |
| } | |
| }); | |
| function generateHeatmapData(colId) { | |
| var values = this.getDataAtCol(colId); | |
| return { | |
| min: Math.min.apply(null, values), | |
| max: Math.max.apply(null, values) | |
| }; | |
| } | |
| // https://stackoverflow.com/questions/22477612/converting-array-of-objects-into-array-of-arrays | |
| function toArray(input) { | |
| var result = input.map(function(obj) { | |
| return Object.keys(obj).map(function(key) { | |
| return obj[key]; | |
| }); | |
| }); | |
| return result; | |
| } | |
| // csv logic adapted from https://github.com/juantascon/jquery-handsontable-csv | |
| function csvString(instance, sep, dec) { | |
| var headers = instance.getColHeader(); | |
| var csv = headers.join(sep) + "\n"; | |
| for (var i = 0; i < instance.countRows(); i++) { | |
| var row = []; | |
| for (var h in headers) { | |
| var col = instance.propToCol(h); | |
| var value = instance.getDataAtRowProp(i, col); | |
| if ( !isNaN(value) ) { | |
| value = value.toString().replace(".", dec); | |
| } | |
| row.push(value); | |
| } | |
| csv += row.join(sep); | |
| csv += "\n"; | |
| } | |
| return csv; | |
| } | |
| function customRenderer(instance, TD, row, col, prop, value, cellProperties) { | |
| if (['date', 'handsontable', 'dropdown'].indexOf(cellProperties.type) > -1) { | |
| type = 'autocomplete'; | |
| } else { | |
| type = cellProperties.type; | |
| } | |
| Handsontable.renderers.getRenderer(type)(instance, TD, row, col, prop, value, cellProperties); | |
| } | |
| function renderSparkline(instance, td, row, col, prop, value, cellProperties) { | |
| try { | |
| val = JSON.parse(value); | |
| nm = 'sparklines_r' + row + '_c' + col; | |
| td.innerHTML = '<span class=\"' + nm + '\"></span>'; | |
| // adjust for cell padding | |
| if (val.options && val.options.type && | |
| ['bar', 'tristate'].indexOf(val.options.type[0]) > -1) { | |
| val.options.barSpacing = 1; | |
| val.options.barWidth = Math.max(1, Math.round((instance.getColWidth(col) - 8 - (val.values.length - 1)) / val.values.length)); | |
| } else { | |
| if (!val.options) { | |
| val.options = {}; | |
| } | |
| val.options.width = (instance.getColWidth(col) - 8) + "px"; | |
| } | |
| $('.' + nm).sparkline(val.values, val.options); | |
| } catch(err) { | |
| td.innerHTML = ''; | |
| } | |
| return td; | |
| } | |
| // http://docs.handsontable.com/0.16.1/demo-custom-renderers.html | |
| function strip_tags(input, allowed) { | |
| var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, | |
| commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi; | |
| // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>) | |
| allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); | |
| return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) { | |
| return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''; | |
| }); | |
| } | |
| function safeHtmlRenderer(instance, td, row, col, prop, value, cellProperties) { | |
| var escaped = Handsontable.helper.stringify(value); | |
| if (instance.getSettings().allowedTags) { | |
| tags = instance.getSettings().allowedTags; | |
| } else { | |
| tags = '<em><b><strong><a><big>'; | |
| } | |
| escaped = strip_tags(escaped, tags); //be sure you only allow certain HTML tags to avoid XSS threats (you should also remove unwanted HTML attributes) | |
| td.innerHTML = escaped; | |
| return td; | |
| } | |
| // if the table is in an ineractive session, shiny mode, | |
| // then we register the handler for setting data interactively | |
| if (HTMLWidgets.shinyMode) { | |
| Shiny.addCustomMessageHandler( | |
| "handler_setDataAtCell", | |
| function(message) { | |
| var hot = window.HTMLWidgets.find('#' + message.id).hot; | |
| var count = message.size; | |
| // setDataAtCell is overloaded so there's different parameters to send one value | |
| if( count == 1 ) { | |
| hot.setDataAtCell(message.row, message.col, message.val); | |
| return; | |
| } | |
| // if sending 2+ values to the table then we use an array of arrays | |
| var datArr = []; | |
| // when the table is sorted we need to send the values to their visual locations | |
| if ( hot.getPlugin('columnSorting').isSorted() ) { | |
| for ( var i=0; i < count; i++ ) { | |
| datArr[datArr.length] = [hot.toVisualRow(message.row[i]), | |
| hot.toVisualColumn(message.col[i]), | |
| message.val[i]]; | |
| } | |
| } else { | |
| // when not sorted we just use the values from R | |
| for ( var i=0; i < count; i++ ) { | |
| datArr[datArr.length] = [message.row[i], message.col[i], message.val[i]]; | |
| } | |
| } | |
| hot.setDataAtCell(datArr); | |
| }); | |
| } |