Permalink
Browse files

Merge branch 'feature/mergedCustomHeaderRendererCellFunctionsGetters'…

… into develop
  • Loading branch information...
2 parents 4107d88 + ea94ac2 commit 3151685512eb236340978edc7b3908bb4bfcc473 @warpech warpech committed Nov 18, 2013
View
492 demo/js/jquery.ui.position.js
@@ -0,0 +1,492 @@
+/*! jQuery UI - v1.10.3 - 2013-11-07
+* http://jqueryui.com
+* Includes: jquery.ui.position.js
+* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */
+
+(function( $, undefined ) {
+
+$.ui = $.ui || {};
+
+var cachedScrollbarWidth,
+ max = Math.max,
+ abs = Math.abs,
+ round = Math.round,
+ rhorizontal = /left|center|right/,
+ rvertical = /top|center|bottom/,
+ roffset = /[\+\-]\d+(\.[\d]+)?%?/,
+ rposition = /^\w+/,
+ rpercent = /%$/,
+ _position = $.fn.position;
+
+function getOffsets( offsets, width, height ) {
+ return [
+ parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
+ parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
+ ];
+}
+
+function parseCss( element, property ) {
+ return parseInt( $.css( element, property ), 10 ) || 0;
+}
+
+function getDimensions( elem ) {
+ var raw = elem[0];
+ if ( raw.nodeType === 9 ) {
+ return {
+ width: elem.width(),
+ height: elem.height(),
+ offset: { top: 0, left: 0 }
+ };
+ }
+ if ( $.isWindow( raw ) ) {
+ return {
+ width: elem.width(),
+ height: elem.height(),
+ offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
+ };
+ }
+ if ( raw.preventDefault ) {
+ return {
+ width: 0,
+ height: 0,
+ offset: { top: raw.pageY, left: raw.pageX }
+ };
+ }
+ return {
+ width: elem.outerWidth(),
+ height: elem.outerHeight(),
+ offset: elem.offset()
+ };
+}
+
+$.position = {
+ scrollbarWidth: function() {
+ if ( cachedScrollbarWidth !== undefined ) {
+ return cachedScrollbarWidth;
+ }
+ var w1, w2,
+ div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
+ innerDiv = div.children()[0];
+
+ $( "body" ).append( div );
+ w1 = innerDiv.offsetWidth;
+ div.css( "overflow", "scroll" );
+
+ w2 = innerDiv.offsetWidth;
+
+ if ( w1 === w2 ) {
+ w2 = div[0].clientWidth;
+ }
+
+ div.remove();
+
+ return (cachedScrollbarWidth = w1 - w2);
+ },
+ getScrollInfo: function( within ) {
+ var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
+ overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
+ hasOverflowX = overflowX === "scroll" ||
+ ( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
+ hasOverflowY = overflowY === "scroll" ||
+ ( overflowY === "auto" && within.height < within.element[0].scrollHeight );
+ return {
+ width: hasOverflowY ? $.position.scrollbarWidth() : 0,
+ height: hasOverflowX ? $.position.scrollbarWidth() : 0
+ };
+ },
+ getWithinInfo: function( element ) {
+ var withinElement = $( element || window ),
+ isWindow = $.isWindow( withinElement[0] );
+ return {
+ element: withinElement,
+ isWindow: isWindow,
+ offset: withinElement.offset() || { left: 0, top: 0 },
+ scrollLeft: withinElement.scrollLeft(),
+ scrollTop: withinElement.scrollTop(),
+ width: isWindow ? withinElement.width() : withinElement.outerWidth(),
+ height: isWindow ? withinElement.height() : withinElement.outerHeight()
+ };
+ }
+};
+
+$.fn.position = function( options ) {
+ if ( !options || !options.of ) {
+ return _position.apply( this, arguments );
+ }
+
+ // make a copy, we don't want to modify arguments
+ options = $.extend( {}, options );
+
+ var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
+ target = $( options.of ),
+ within = $.position.getWithinInfo( options.within ),
+ scrollInfo = $.position.getScrollInfo( within ),
+ collision = ( options.collision || "flip" ).split( " " ),
+ offsets = {};
+
+ dimensions = getDimensions( target );
+ if ( target[0].preventDefault ) {
+ // force left top to allow flipping
+ options.at = "left top";
+ }
+ targetWidth = dimensions.width;
+ targetHeight = dimensions.height;
+ targetOffset = dimensions.offset;
+ // clone to reuse original targetOffset later
+ basePosition = $.extend( {}, targetOffset );
+
+ // force my and at to have valid horizontal and vertical positions
+ // if a value is missing or invalid, it will be converted to center
+ $.each( [ "my", "at" ], function() {
+ var pos = ( options[ this ] || "" ).split( " " ),
+ horizontalOffset,
+ verticalOffset;
+
+ if ( pos.length === 1) {
+ pos = rhorizontal.test( pos[ 0 ] ) ?
+ pos.concat( [ "center" ] ) :
+ rvertical.test( pos[ 0 ] ) ?
+ [ "center" ].concat( pos ) :
+ [ "center", "center" ];
+ }
+ pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
+ pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
+
+ // calculate offsets
+ horizontalOffset = roffset.exec( pos[ 0 ] );
+ verticalOffset = roffset.exec( pos[ 1 ] );
+ offsets[ this ] = [
+ horizontalOffset ? horizontalOffset[ 0 ] : 0,
+ verticalOffset ? verticalOffset[ 0 ] : 0
+ ];
+
+ // reduce to just the positions without the offsets
+ options[ this ] = [
+ rposition.exec( pos[ 0 ] )[ 0 ],
+ rposition.exec( pos[ 1 ] )[ 0 ]
+ ];
+ });
+
+ // normalize collision option
+ if ( collision.length === 1 ) {
+ collision[ 1 ] = collision[ 0 ];
+ }
+
+ if ( options.at[ 0 ] === "right" ) {
+ basePosition.left += targetWidth;
+ } else if ( options.at[ 0 ] === "center" ) {
+ basePosition.left += targetWidth / 2;
+ }
+
+ if ( options.at[ 1 ] === "bottom" ) {
+ basePosition.top += targetHeight;
+ } else if ( options.at[ 1 ] === "center" ) {
+ basePosition.top += targetHeight / 2;
+ }
+
+ atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
+ basePosition.left += atOffset[ 0 ];
+ basePosition.top += atOffset[ 1 ];
+
+ return this.each(function() {
+ var collisionPosition, using,
+ elem = $( this ),
+ elemWidth = elem.outerWidth(),
+ elemHeight = elem.outerHeight(),
+ marginLeft = parseCss( this, "marginLeft" ),
+ marginTop = parseCss( this, "marginTop" ),
+ collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
+ collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
+ position = $.extend( {}, basePosition ),
+ myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
+
+ if ( options.my[ 0 ] === "right" ) {
+ position.left -= elemWidth;
+ } else if ( options.my[ 0 ] === "center" ) {
+ position.left -= elemWidth / 2;
+ }
+
+ if ( options.my[ 1 ] === "bottom" ) {
+ position.top -= elemHeight;
+ } else if ( options.my[ 1 ] === "center" ) {
+ position.top -= elemHeight / 2;
+ }
+
+ position.left += myOffset[ 0 ];
+ position.top += myOffset[ 1 ];
+
+ // if the browser doesn't support fractions, then round for consistent results
+ if ( !$.support.offsetFractions ) {
+ position.left = round( position.left );
+ position.top = round( position.top );
+ }
+
+ collisionPosition = {
+ marginLeft: marginLeft,
+ marginTop: marginTop
+ };
+
+ $.each( [ "left", "top" ], function( i, dir ) {
+ if ( $.ui.position[ collision[ i ] ] ) {
+ $.ui.position[ collision[ i ] ][ dir ]( position, {
+ targetWidth: targetWidth,
+ targetHeight: targetHeight,
+ elemWidth: elemWidth,
+ elemHeight: elemHeight,
+ collisionPosition: collisionPosition,
+ collisionWidth: collisionWidth,
+ collisionHeight: collisionHeight,
+ offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
+ my: options.my,
+ at: options.at,
+ within: within,
+ elem : elem
+ });
+ }
+ });
+
+ if ( options.using ) {
+ // adds feedback as second argument to using callback, if present
+ using = function( props ) {
+ var left = targetOffset.left - position.left,
+ right = left + targetWidth - elemWidth,
+ top = targetOffset.top - position.top,
+ bottom = top + targetHeight - elemHeight,
+ feedback = {
+ target: {
+ element: target,
+ left: targetOffset.left,
+ top: targetOffset.top,
+ width: targetWidth,
+ height: targetHeight
+ },
+ element: {
+ element: elem,
+ left: position.left,
+ top: position.top,
+ width: elemWidth,
+ height: elemHeight
+ },
+ horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
+ vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
+ };
+ if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
+ feedback.horizontal = "center";
+ }
+ if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
+ feedback.vertical = "middle";
+ }
+ if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
+ feedback.important = "horizontal";
+ } else {
+ feedback.important = "vertical";
+ }
+ options.using.call( this, props, feedback );
+ };
+ }
+
+ elem.offset( $.extend( position, { using: using } ) );
+ });
+};
+
+$.ui.position = {
+ fit: {
+ left: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
+ outerWidth = within.width,
+ collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+ overLeft = withinOffset - collisionPosLeft,
+ overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
+ newOverRight;
+
+ // element is wider than within
+ if ( data.collisionWidth > outerWidth ) {
+ // element is initially over the left side of within
+ if ( overLeft > 0 && overRight <= 0 ) {
+ newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
+ position.left += overLeft - newOverRight;
+ // element is initially over right side of within
+ } else if ( overRight > 0 && overLeft <= 0 ) {
+ position.left = withinOffset;
+ // element is initially over both left and right sides of within
+ } else {
+ if ( overLeft > overRight ) {
+ position.left = withinOffset + outerWidth - data.collisionWidth;
+ } else {
+ position.left = withinOffset;
+ }
+ }
+ // too far left -> align with left edge
+ } else if ( overLeft > 0 ) {
+ position.left += overLeft;
+ // too far right -> align with right edge
+ } else if ( overRight > 0 ) {
+ position.left -= overRight;
+ // adjust based on position and margin
+ } else {
+ position.left = max( position.left - collisionPosLeft, position.left );
+ }
+ },
+ top: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
+ outerHeight = data.within.height,
+ collisionPosTop = position.top - data.collisionPosition.marginTop,
+ overTop = withinOffset - collisionPosTop,
+ overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
+ newOverBottom;
+
+ // element is taller than within
+ if ( data.collisionHeight > outerHeight ) {
+ // element is initially over the top of within
+ if ( overTop > 0 && overBottom <= 0 ) {
+ newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
+ position.top += overTop - newOverBottom;
+ // element is initially over bottom of within
+ } else if ( overBottom > 0 && overTop <= 0 ) {
+ position.top = withinOffset;
+ // element is initially over both top and bottom of within
+ } else {
+ if ( overTop > overBottom ) {
+ position.top = withinOffset + outerHeight - data.collisionHeight;
+ } else {
+ position.top = withinOffset;
+ }
+ }
+ // too far up -> align with top
+ } else if ( overTop > 0 ) {
+ position.top += overTop;
+ // too far down -> align with bottom edge
+ } else if ( overBottom > 0 ) {
+ position.top -= overBottom;
+ // adjust based on position and margin
+ } else {
+ position.top = max( position.top - collisionPosTop, position.top );
+ }
+ }
+ },
+ flip: {
+ left: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.offset.left + within.scrollLeft,
+ outerWidth = within.width,
+ offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
+ collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+ overLeft = collisionPosLeft - offsetLeft,
+ overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
+ myOffset = data.my[ 0 ] === "left" ?
+ -data.elemWidth :
+ data.my[ 0 ] === "right" ?
+ data.elemWidth :
+ 0,
+ atOffset = data.at[ 0 ] === "left" ?
+ data.targetWidth :
+ data.at[ 0 ] === "right" ?
+ -data.targetWidth :
+ 0,
+ offset = -2 * data.offset[ 0 ],
+ newOverRight,
+ newOverLeft;
+
+ if ( overLeft < 0 ) {
+ newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
+ if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
+ position.left += myOffset + atOffset + offset;
+ }
+ }
+ else if ( overRight > 0 ) {
+ newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
+ if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
+ position.left += myOffset + atOffset + offset;
+ }
+ }
+ },
+ top: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.offset.top + within.scrollTop,
+ outerHeight = within.height,
+ offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
+ collisionPosTop = position.top - data.collisionPosition.marginTop,
+ overTop = collisionPosTop - offsetTop,
+ overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
+ top = data.my[ 1 ] === "top",
+ myOffset = top ?
+ -data.elemHeight :
+ data.my[ 1 ] === "bottom" ?
+ data.elemHeight :
+ 0,
+ atOffset = data.at[ 1 ] === "top" ?
+ data.targetHeight :
+ data.at[ 1 ] === "bottom" ?
+ -data.targetHeight :
+ 0,
+ offset = -2 * data.offset[ 1 ],
+ newOverTop,
+ newOverBottom;
+ if ( overTop < 0 ) {
+ newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
+ if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
+ position.top += myOffset + atOffset + offset;
+ }
+ }
+ else if ( overBottom > 0 ) {
+ newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
+ if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
+ position.top += myOffset + atOffset + offset;
+ }
+ }
+ }
+ },
+ flipfit: {
+ left: function() {
+ $.ui.position.flip.left.apply( this, arguments );
+ $.ui.position.fit.left.apply( this, arguments );
+ },
+ top: function() {
+ $.ui.position.flip.top.apply( this, arguments );
+ $.ui.position.fit.top.apply( this, arguments );
+ }
+ }
+};
+
+// fraction support test
+(function () {
+ var testElement, testElementParent, testElementStyle, offsetLeft, i,
+ body = document.getElementsByTagName( "body" )[ 0 ],
+ div = document.createElement( "div" );
+
+ //Create a "fake body" for testing based on method used in jQuery.support
+ testElement = document.createElement( body ? "div" : "body" );
+ testElementStyle = {
+ visibility: "hidden",
+ width: 0,
+ height: 0,
+ border: 0,
+ margin: 0,
+ background: "none"
+ };
+ if ( body ) {
+ $.extend( testElementStyle, {
+ position: "absolute",
+ left: "-1000px",
+ top: "-1000px"
+ });
+ }
+ for ( i in testElementStyle ) {
+ testElement.style[ i ] = testElementStyle[ i ];
+ }
+ testElement.appendChild( div );
+ testElementParent = body || document.documentElement;
+ testElementParent.insertBefore( testElement, testElementParent.firstChild );
+
+ div.style.cssText = "position: absolute; left: 10.7432222px;";
+
+ offsetLeft = $( div ).offset().left;
+ $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
+
+ testElement.innerHTML = "";
+ testElementParent.removeChild( testElement );
+})();
+
+}( jQuery ) );
View
168 demo/renderers_html.html
@@ -12,6 +12,7 @@
<link data-jsfiddle="common" rel="stylesheet" media="screen" href="../dist/jquery.handsontable.full.css">
<!-- the below is only needed for DateCell (uses jQuery UI Datepicker) -->
<script data-jsfiddle="common" src="../lib/jquery-ui/js/jquery-ui.custom.min.js"></script>
+ <script data-jsfiddle="common" src="js/jquery.ui.position.js"></script>
<link data-jsfiddle="common" rel="stylesheet" media="screen"
href="../lib/jquery-ui/css/ui-bootstrap/jquery-ui.custom.css">
@@ -274,12 +275,13 @@
.changeType {
border: 1px solid #bbb;
color: #bbb;
+ background: #eee;
border-radius: 2px;
padding: 2px;
font-size: 9px;
float: right;
- line-height: 12px;
+ line-height: 9px;
margin: 3px 3px 0 0;
}
@@ -292,95 +294,99 @@
.changeType.pressed {
background-color: #999;
}
- </style>
- <script data-jsfiddle="example3">
- //TODO fix the colHeader menu demo. I commented it out because it caused exceptions since 0.10.0-beta3
- /*
- var HOT
- , changeCol
- , columns = [
- {type: 'numeric'},
- {type: 'numeric'},
- {type: 'numeric'},
- {type: 'numeric'},
- {type: 'numeric'},
- {type: 'numeric'}
- ];
-
- function contextMenuItem(name, value) {
- return {name: name, type: "radio", radio: "typeRadio", value: value, events: {
- change: function () {
- $(this).trigger('contextmenu:hide');
- }
- }};
+ .changeTypeMenu {
+ position: absolute;
+ border: 1px solid #ccc;
+ margin-top: 18px;
+ box-shadow: 0 1px 3px -1px #323232;
+ background: white;
+ padding: 0;
+ display: none;
+ z-index: 10;
}
- $.contextMenu({
- selector: '.changeType',
- items: {
- "text": contextMenuItem("Text", "text"),
- "numeric": contextMenuItem("Numeric", "numeric"),
- "date": contextMenuItem("Date", "date")
- },
- events: {
- show: function (opt) {
- $.contextMenu.setInputValues(opt, {typeRadio: getColumnType(changeCol)});
- },
- hide: function (opt) {
- var values = $.contextMenu.getInputValues(opt);
- setColumnType(changeCol, values.typeRadio);
- }
- }
- });
+ .changeTypeMenu li{
+ text-align: left;
+ list-style: none;
+ padding: 2px 20px;
+ cursor: pointer;
+ }
- function getColumnType(i) {
- return columns[i].type;
+ .changeTypeMenu li.active:before {
+ content: "\2714";
+ margin-left: -15px;
+ margin-right: 3px;
}
- function setColumnType(i, type) {
- columns[i].type = type;
- HOT.updateSettings({columns: columns});
- HOT.render();
+ .changeTypeMenu li:hover {
+ background: #eee;
}
+
+ </style>
+
+ <script data-jsfiddle="example3">
var data = [
["", "Maserati", "Mazda", "Mercedes", "Mini", "Mitsubishi"],
["2009", 0, 2941, 4303, 354, 5814],
- ["2010", "fff", 2905, 2867, 412, 5284],
+ ["2010", 3, 2905, 2867, 412, 5284],
["2011", 4, 2517, 4822, 552, 6127],
["2012", 2, 2422, 5399, 776, 4151]
];
+ var columns = [
+ {type: 'numeric'},
+ {type: 'numeric'},
+ {type: 'numeric'},
+ {type: 'numeric'},
+ {type: 'numeric'},
+ {type: 'numeric'}
+ ];
+
+
$('#example3').handsontable({
data: data,
- minRows: 5,
- minCols: 6,
- minSpareRows: 1,
- autoWrapRow: true,
colHeaders: true,
+ minSpareRows: 1,
+ type: 'numeric',
columns: columns,
- contextMenu: true,
- allowInvalid: true,
afterGetColHeader: function (col, TH) {
- var BUTTON = document.createElement('DIV');
- BUTTON.className = "changeType";
- BUTTON.appendChild(document.createTextNode('\u25BC')); //adds BLACK DOWN-POINTING TRIANGLE icon
+ var instance = this;
+ var menu = buildMenu(columns[col].type);
- var $button = $(BUTTON);
- $button.click(function () {
- changeCol = col;
+ var $button = buildButton();
+ $button.click(function (e) {
- var pos = $button.offset();
- $button.contextMenu({x: pos.left, y: pos.top + $button.outerHeight() });
- BUTTON.className = "changeType pressed";
+ e.preventDefault();
+ e.stopImmediatePropagation();
- $(document.body).one("contextmenu:hide", function () {
- BUTTON.className = "changeType";
+ $('.changeTypeMenu').hide();
+
+ menu.show();
+
+ menu.position({
+ my: 'left top',
+ at: 'left bottom',
+ of: $button,
+ within: instance.rootElement
});
+
+ $(document).off('click.changeTypeMenu.hide');
+
+ $(document).one('click.changeTypeMenu.hide', function () {
+ menu.hide();
+ });
+
+
});
- TH.firstChild.appendChild(BUTTON);
+ menu.on('click', 'li', function () {
+ setColumnType(col, $(this).data('colType'), instance);
+ });
+
+ TH.firstChild.appendChild($button[0]);
+ TH.appendChild(menu[0]);
},
cells: function (row, col, prop) {
if (row === 0) {
@@ -392,8 +398,38 @@
}
});
- HOT = $('#example3').handsontable('getInstance');
- */
+ function buildMenu(activeCellType){
+ var menu = $('<ul></ul>').addClass('changeTypeMenu');
+
+ $.each(['text', 'numeric', 'date'], function (i, type) {
+ var item = $('<li></li>').data('colType', type).text(type);
+
+ if(activeCellType == type){
+ item.addClass('active');
+ }
+
+ menu.append(item);
+
+ });
+
+
+ return menu;
+
+ }
+
+ function buildButton() {
+ return $('<button></button>').addClass('changeType').html('\u25BC');
+ }
+
+ function setColumnType(i, type, instance) {
+ columns[i].type = type;
+ instance.updateSettings({columns: columns});
+ instance.validateCells(function(){
+ instance.render();
+ });
+ }
+
+
</script>
</div>
</div>
View
43 src/core.js
@@ -14,12 +14,9 @@ Handsontable.Core = function (rootElement, userSettings) {
, editorManager
, autofill
, instance = this
- , GridSettings = function () {
- };
+ , GridSettings = function () {};
Handsontable.helper.inherit(GridSettings, DefaultSettings); //create grid settings as a copy of default settings
- Handsontable.helper.extend(GridSettings.prototype, Handsontable.TextCell); //overwrite defaults with default cell
- expandType(userSettings);
Handsontable.helper.extend(GridSettings.prototype, userSettings); //overwrite defaults with user settings
this.rootElement = rootElement;
@@ -1317,17 +1314,18 @@ Handsontable.Core = function (rootElement, userSettings) {
changes.splice(i, 1);
}
else {
+ var row = changes[i][0];
var col = datamap.propToCol(changes[i][1]);
var logicalCol = instance.runHooksAndReturn('modifyCol', col); //column order may have changes, so we need to translate physical col index (stored in datasource) to logical (displayed to user)
- var cellProperties = instance.getCellMeta(changes[i][0], logicalCol);
+ var cellProperties = instance.getCellMeta(row, logicalCol);
- if (cellProperties.dataType === 'number' && typeof changes[i][3] === 'string') {
+ if (cellProperties.type === 'numeric' && typeof changes[i][3] === 'string') {
if (changes[i][3].length > 0 && /^-?[\d\s]*\.?\d*$/.test(changes[i][3])) {
changes[i][3] = numeral().unformat(changes[i][3] || '0'); //numeral cannot unformat empty string
}
}
- if (cellProperties.validator) {
+ if (instance.getCellValidator(cellProperties)) {
waitingForValidator.addValidatorToQueue();
instance.validateCell(changes[i][3], cellProperties, (function (i, cellProperties) {
return function (result) {
@@ -1407,7 +1405,7 @@ Handsontable.Core = function (rootElement, userSettings) {
}
this.validateCell = function (value, cellProperties, callback, source) {
- var validator = cellProperties.validator;
+ var validator = instance.getCellValidator(cellProperties);
if (Object.prototype.toString.call(validator) === '[object RegExp]') {
validator = (function (validator) {
@@ -1826,7 +1824,7 @@ Handsontable.Core = function (rootElement, userSettings) {
// Use settings provided by user
if (GridSettings.prototype.columns) {
column = GridSettings.prototype.columns[i];
- expandType(column);
+// expandType(column);
Handsontable.helper.extend(proto, column);
}
}
@@ -2051,13 +2049,13 @@ Handsontable.Core = function (rootElement, userSettings) {
cellProperties.instance = instance;
instance.PluginHooks.run('beforeGetCellMeta', row, col, cellProperties);
- expandType(cellProperties); //for `type` added in beforeGetCellMeta
+// expandType(cellProperties); //for `type` added in beforeGetCellMeta
if (cellProperties.cells) {
var settings = cellProperties.cells.call(cellProperties, row, col, prop);
if (settings) {
- expandType(settings); //for `type` added in cells
+// expandType(settings); //for `type` added in cells
Handsontable.helper.extend(cellProperties, settings);
}
}
@@ -2091,6 +2089,22 @@ Handsontable.Core = function (rootElement, userSettings) {
}
};
+ this.getCellRenderer = function (row, col) {
+ var renderer = Handsontable.helper.cellMethodLookupFactory('renderer').call(this, row, col);
+
+ if(typeof renderer == 'string'){
+ renderer = Handsontable.cellLookup.renderer[renderer];
+ }
+
+ return renderer
+
+ };
+
+ this.getCellEditor = Handsontable.helper.cellMethodLookupFactory('editor');
+
+ this.getCellValidator = Handsontable.helper.cellMethodLookupFactory('validator');
+
+
/**
* Validates all cells using their validator functions and calls callback when finished. Does not render the view
* @param callback
@@ -2559,8 +2573,8 @@ Handsontable.Core = function (rootElement, userSettings) {
this.version = '@@version'; //inserted by grunt from package.json
};
-var DefaultSettings = function () {
-};
+var DefaultSettings = function () {};
+
DefaultSettings.prototype = {
data: void 0,
width: void 0,
@@ -2596,7 +2610,8 @@ DefaultSettings.prototype = {
invalidCellClassName: 'htInvalid',
fragmentSelection: false,
readOnly: false,
- nativeScrollbars: false
+ nativeScrollbars: false,
+ type: 'text'
};
Handsontable.DefaultSettings = DefaultSettings;
View
2 src/editorManager.js
@@ -358,7 +358,7 @@
var originalValue = instance.getDataAtCell(row, col);
var cellProperties = instance.getCellMeta(row, col);
- var editorClass = cellProperties.editor;
+ var editorClass = instance.getCellEditor(cellProperties);
activeEditor = Handsontable.editors.getEditor(editorClass, instance);
activeEditor.prepare(row, col, prop, td, originalValue, cellProperties);
View
18 src/editors/baseEditor.js
@@ -23,9 +23,7 @@
this._closeCallback(result);
}
- BaseEditor.prototype.init = function(){
- throw Error('Editor init() method unimplemented');
- };
+ BaseEditor.prototype.init = function(){};
BaseEditor.prototype.val = function(newValue){
throw Error('Editor val() method unimplemented');
@@ -56,7 +54,17 @@
baseClass.apply(this, arguments);
}
- return Handsontable.helper.inherit(Editor, baseClass);
+ function inherit(Child, Parent){
+ function Bridge() {
+ }
+
+ Bridge.prototype = Parent.prototype;
+ Child.prototype = new Bridge();
+ Child.prototype.constructor = Child;
+ return Child;
+ }
+
+ return inherit(Editor, baseClass);
};
BaseEditor.prototype.saveValue = function (val, ctrlDown) {
@@ -135,7 +143,7 @@
this.saveValue(val, ctrlDown);
- if(this.cellProperties.validator){
+ if(this.instance.getCellValidator(this.cellProperties)){
var that = this;
this.instance.addHookOnce('afterValidate', function (result) {
that.state = Handsontable.EditorState.FINISHED;
View
118 src/helpers.js
@@ -159,11 +159,8 @@ Handsontable.helper.randomString = function () {
* @return {Object} extended Child
*/
Handsontable.helper.inherit = function (Child, Parent) {
- function Bridge() {
- }
-
- Bridge.prototype = Parent.prototype;
- Child.prototype = new Bridge();
+ Parent.prototype.constructor = Parent;
+ Child.prototype = new Parent();
Child.prototype.constructor = Child;
return Child;
};
@@ -181,21 +178,46 @@ Handsontable.helper.extend = function (target, extension) {
}
};
+Handsontable.helper.getPrototypeOf = function (obj) {
+ var prototype;
+
+ if(typeof obj.__proto__ == "object"){
+ prototype = obj.__proto__;
+ } else {
+ var oldConstructor,
+ constructor = obj.constructor;
+
+ if (typeof obj.constructor == "function") {
+ oldConstructor = constructor;
+
+ if (delete obj.constructor){
+ constructor = obj.constructor; // get real constructor
+ obj.constructor = oldConstructor; // restore constructor
+ }
+
+
+ }
+
+ prototype = constructor ? constructor.prototype : null; // needed for IE
+
+ }
+
+ return prototype;
+};
+
/**
* Factory for columns constructors.
* @param {Object} GridSettings
* @param {Array} conflictList
* @return {Object} ColumnSettings
*/
Handsontable.helper.columnFactory = function (GridSettings, conflictList) {
- var i = 0, len = conflictList.length, ColumnSettings = function () {
- };
+ function ColumnSettings () {}
- // Inherit prototype from grid settings
- ColumnSettings.prototype = new GridSettings();
+ Handsontable.helper.inherit(ColumnSettings, GridSettings);
// Clear conflict settings
- for (; i < len; i++) {
+ for (var i = 0, len = conflictList.length; i < len; i++) {
ColumnSettings.prototype[conflictList[i]] = void 0;
}
@@ -241,37 +263,6 @@ Handsontable.helper.extendArray = function (arr, extension) {
};
/**
- * Returns cell renderer or editor function directly or through lookup map
- */
-Handsontable.helper.getCellMethod = function (methodName, methodFunction) {
- if (typeof methodFunction === 'string') {
- var result = Handsontable.cellLookup[methodName][methodFunction];
- if (result === void 0 && methodName === 'renderer') {
- return function (instance, TD, row, col, prop, value, cellProperties) {
- cellProperties.rendererTemplate = methodFunction;
- var editor = cellProperties.editor;
- /*
- In future
- 1. remove AutocompleteRenderer
- 2. make the "arrow" be added by editor itself using a plugin hook inserted in tableView.js (at the end of cellRenderer: ...)
- 3. editor add the arrow if cellProperty.arrow === "true"
- */
- if (editor === Handsontable.HandsontableEditor || editor === Handsontable.DateEditor || editor === Handsontable.AutocompleteEditor) {
- return Handsontable.AutocompleteRenderer.apply(instance, arguments);
- }
- else {
- return Handsontable.TextRenderer.apply(instance, arguments);
- }
- }
- }
- return result;
- }
- else {
- return methodFunction;
- }
-};
-
-/**
* Determines if the given DOM element is an input field placed outside of HOT.
* Notice: By 'input' we mean input, textarea and select nodes
* @param element - DOM element
@@ -370,4 +361,49 @@ Handsontable.helper.proxy = function (fun, context) {
return function () {
return fun.apply(context, arguments);
};
+};
+
+Handsontable.helper.cellMethodLookupFactory = function (methodName) {
+
+ return function cellMethodLookup (row, col) {
+
+ return (function getMethodFromProperties(properties) {
+
+ if (!properties){
+
+ return; //method not found
+
+ }
+ else if(properties.hasOwnProperty(methodName)){
+
+ return properties[methodName]; //method defined directly
+
+ } else if(properties.hasOwnProperty('type')){
+
+ var type;
+
+ if(typeof properties.type != 'string' ){
+ throw new Error('Cell type must be a string ');
+ }
+
+ type = translateTypeNameToObject(properties.type);
+
+ return type[methodName]; //method defined in type. if does not exist (eg. validator), returns undefined
+ }
+
+ return getMethodFromProperties(Handsontable.helper.getPrototypeOf(properties));
+
+ })(typeof row == 'number' ? this.getCellMeta(row, col) : row);
+
+ };
+
+ function translateTypeNameToObject(typeName) {
+ var type = Handsontable.cellTypes[typeName];
+
+ if(typeof type == 'undefined'){
+ throw new Error('You declared cell type "' + typeName + '" as a string that is not mapped to a known object. Cell type must be an object or a string mapped to an object in Handsontable.cellTypes');
+ }
+
+ return type;
+ }
};
View
2 src/plugins/autoColumnSize.js
@@ -86,7 +86,7 @@
instance.view.wt.wtDom.empty(tmp.tbody);
var cellProperties = instance.getCellMeta(0, col);
- var renderer = Handsontable.helper.getCellMethod('renderer', cellProperties.renderer);
+ var renderer = instance.getCellRenderer(cellProperties);
for (var i in samples) {
if (samples.hasOwnProperty(i)) {
View
29 src/tableView.js
@@ -139,8 +139,18 @@ Handsontable.TableView = function (instance) {
}] : []
},
columnWidth: instance.getColWidth,
- cellRenderer: function (row, column, TD) {
- that.applyCellTypeMethod('renderer', TD, row, column);
+ cellRenderer: function (row, col, TD) {
+
+ var prop = that.instance.colToProp(col)
+ , cellProperties = that.instance.getCellMeta(row, col)
+ , renderer = that.instance.getCellRenderer(cellProperties)
+
+ var value = that.instance.getDataAtRowProp(row, prop);
+
+ renderer(that.instance, TD, row, col, prop, value, cellProperties);
+
+ that.instance.PluginHooks.run('afterRenderer', TD, row, col, prop, value, cellProperties);
+
},
selections: {
current: {
@@ -310,21 +320,6 @@ Handsontable.TableView.prototype.render = function () {
this.instance.rootElement.triggerHandler('render.handsontable');
};
-Handsontable.TableView.prototype.applyCellTypeMethod = function (methodName, td, row, col) {
- var prop = this.instance.colToProp(col)
- , cellProperties = this.instance.getCellMeta(row, col)
- , method = Handsontable.helper.getCellMethod(methodName, cellProperties[methodName]); //methodName is 'renderer' or 'editor'
-
- var value = this.instance.getDataAtRowProp(row, prop);
- var res = method(this.instance, td, row, col, prop, value, cellProperties);
-
- if (methodName === 'renderer') {
- this.instance.PluginHooks.run('afterRenderer', td, row, col, prop, value, cellProperties);
- }
-
- return res;
-};
-
/**
* Returns td object given coordinates
*/
View
5 src/validators/numericValidator.js
@@ -1,8 +1,11 @@
/**
* Numeric cell validator
* @param {*} value - Value of edited cell
- * @param {*} calback - Callback called with validation result
+ * @param {*} callback - Callback called with validation result
*/
Handsontable.NumericValidator = function (value, callback) {
+ if (value === null) {
+ value = '';
+ }
callback(/^-?\d*\.?\d*$/.test(value));
};
View
19 test/jasmine/spec/Core_getCellMetaSpec.js
@@ -48,15 +48,13 @@ describe('Core_getCellMeta', function () {
handsontable({
cells: function () {
return {
- type: {
- renderer: function (instance, td, row, col, prop, value, cellProperties) {
+ renderer: function (instance, td, row, col, prop, value, cellProperties) {
//taken from demo/renderers.html
Handsontable.TextCell.renderer.apply(this, arguments);
$(td).css({
background: 'yellow'
});
}
- }
}
}
});
@@ -111,7 +109,6 @@ describe('Core_getCellMeta', function () {
expect(called).toBeGreaterThan(0);
expect(_this.row).toEqual(_row);
- expect(_this.renderer).toBe(Handsontable.TextRenderer);
expect(_this.instance).toBe(HOT);
});
@@ -163,18 +160,4 @@ describe('Core_getCellMeta', function () {
});
- it('should inherit readOnly from cell type (legacy)', function () {
- handsontable({
- data : [[1,2]],
- cells : function (row, col, prop) {
- return {
- type : {
- readOnly: true
- }
- }
- }
- });
- expect(getCellMeta(0, 0).readOnly).toEqual(true);
- });
-
});
View
2 test/jasmine/spec/Core_renderSpec.js
@@ -30,7 +30,7 @@ describe('Core_render', function () {
minSpareCols: 4,
cells: function () {
return {
- type: {renderer: greenCell}
+ renderer: greenCell
};
}
});
View
77 test/jasmine/spec/Core_updateSpec.js
@@ -148,5 +148,82 @@ describe('Core_updateSettings', function () {
expect($(getCell(0, 1)).hasClass('htDimmed')).toBe(false);
});
+ it("should not alter the columns object during init", function () {
+
+ var columns = [
+ {
+ type: 'text'
+ }
+ ];
+
+ var columnsCopy = JSON.parse(JSON.stringify(columns));
+
+ handsontable({
+ columns: columns
+ });
+
+ expect(columns).toEqual(columnsCopy);
+
+
+ });
+
+ it("should update column type", function () {
+
+ var columns = [
+ {
+ type: 'text'
+ }
+ ];
+
+ handsontable({
+ columns: columns
+ });
+
+ expect(getCellMeta(0, 0).type).toEqual('text');
+ expect(getCellRenderer(0, 0)).toBe(Handsontable.TextCell.renderer);
+ expect(getCellEditor(0, 0)).toBe(Handsontable.TextCell.editor);
+
+ columns[0].type = 'date';
+
+ updateSettings({
+ columns: columns
+ });
+
+ expect(getCellMeta(0, 0).type).toEqual('date');
+ expect(getCellRenderer(0, 0)).toBe(Handsontable.DateCell.renderer);
+ expect(getCellEditor(0, 0)).toEqual(Handsontable.DateCell.editor);
+
+ });
+
+ it("should update cell type functions, even if new type does not implement all of those functions", function () {
+
+ var columns = [
+ {
+ type: 'numeric'
+ }
+ ];
+
+ handsontable({
+ columns: columns
+ });
+
+ expect(getCellMeta(0, 0).type).toEqual('numeric');
+ expect(getCellRenderer(0, 0)).toBe(Handsontable.NumericCell.renderer);
+ expect(getCellEditor(0, 0)).toBe(Handsontable.NumericCell.editor);
+ expect(getCellValidator(0, 0)).toBe(Handsontable.NumericCell.validator);
+
+ columns[0].type = 'text';
+
+ updateSettings({
+ columns: columns
+ });
+
+ expect(getCellMeta(0, 0).type).toEqual('text');
+ expect(getCellRenderer(0, 0)).toBe(Handsontable.TextCell.renderer);
+ expect(getCellEditor(0, 0)).toEqual(Handsontable.TextCell.editor);
+ expect(Handsontable.TextCell.validator).toBeUndefined();
+ expect(getCellValidator(0, 0)).toBeUndefined();
+
+ });
});
View
19 test/jasmine/spec/Core_validateSpec.js
@@ -245,18 +245,20 @@ describe('Core_validate', function () {
it('should add class name `htInvalid` to a cell without removing other classes', function () {
var onAfterValidate = jasmine.createSpy('onAfterValidate');
+ var validator = jasmine.createSpy('validator').andCallThrough();
+ validator.plan = function (value, callb) {
+ if (value == 123) {
+ callb(false)
+ }
+ else {
+ callb(true)
+ }
+ };
handsontable({
data: createSpreadsheetData(2, 2),
type: 'numeric',
- validator: function (value, callb) {
- if (value == 123) {
- callb(false)
- }
- else {
- callb(true)
- }
- },
+ validator: validator,
afterValidate: onAfterValidate
});
@@ -267,6 +269,7 @@ describe('Core_validate', function () {
}, 'Cell validation 1', 1000);
runs(function () {
+ expect(validator.calls.length).toEqual(1);
expect(this.$container.find('tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(true);
expect(this.$container.find('tr:eq(0) td:eq(0)').hasClass('htNumeric')).toEqual(true);
View
3 test/jasmine/spec/SpecHelper.js
@@ -249,6 +249,9 @@ var setDataAtCell = handsontableMethodFactory('setDataAtCell');
var setDataAtRowProp = handsontableMethodFactory('setDataAtRowProp');
var getCell = handsontableMethodFactory('getCell');
var getCellMeta = handsontableMethodFactory('getCellMeta');
+var getCellRenderer = handsontableMethodFactory('getCellRenderer');
+var getCellEditor = handsontableMethodFactory('getCellEditor');
+var getCellValidator = handsontableMethodFactory('getCellValidator');
var getData = handsontableMethodFactory('getData');
var getDataAtCell = handsontableMethodFactory('getDataAtCell');
var getDataAtRowProp = handsontableMethodFactory('getDataAtRowProp');
View
3 test/jasmine/spec/plugins/manualColumnMoveSpec.js
@@ -267,7 +267,8 @@ describe('manualColumnMove', function () {
columns: [
{
type: 'numeric',
- data: 'id'},
+ data: 'id'
+ },
{
data: 'name'
},
View
24 test/jasmine/spec/validators/numericValidatorSpec.js
@@ -102,4 +102,28 @@ describe('NumericValidator', function () {
});
});
+ it('should validate empty string', function () {
+ var out;
+
+ Handsontable.NumericValidator('', function (result) {
+ out = result;
+ });
+
+ expect(out).toBe(true);
+ });
+
+ //is this correct behavior is disputable, but at least it's consistent
+ it('should validate null with the same empty string', function () {
+ var out1, out2;
+
+ Handsontable.NumericValidator('', function (result) {
+ out1 = result;
+ });
+
+ Handsontable.NumericValidator(null, function (result) {
+ out2 = result;
+ });
+
+ expect(out1).toBe(out2);
+ });
});

0 comments on commit 3151685

Please sign in to comment.