You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
608 lines
19 KiB
608 lines
19 KiB
/**
|
|
* ng-handsontable 0.13.0
|
|
*
|
|
* Copyright 2012-2015 Marcin Warpechowski
|
|
* Copyright 2015 Handsoncode sp. z o.o. <hello@handsontable.com>
|
|
* Licensed under the MIT license.
|
|
* https://github.com/handsontable/ngHandsontable
|
|
* Date: Wed Oct 26 2016 10:00:05 GMT+0200 (CEST)
|
|
*/
|
|
|
|
if (document.all && !document.addEventListener) { // IE 8 and lower
|
|
document.createElement('hot-table');
|
|
document.createElement('hot-column');
|
|
document.createElement('hot-autocomplete');
|
|
}
|
|
|
|
angular.module('ngHandsontable.services', []);
|
|
angular.module('ngHandsontable.directives', []);
|
|
angular.module('ngHandsontable', [
|
|
'ngHandsontable.services',
|
|
'ngHandsontable.directives'
|
|
]);
|
|
|
|
Handsontable.hooks.add('afterContextMenuShow', function() {
|
|
Handsontable.eventManager.isHotTableEnv = false;
|
|
});
|
|
|
|
(function() {
|
|
function autoCompleteFactory($parse) {
|
|
return {
|
|
parseAutoComplete: function(column, dataSet, propertyOnly) {
|
|
column.source = function(query, process) {
|
|
var row = this.instance.getSelected()[0];
|
|
var source = [];
|
|
var data = dataSet[row];
|
|
|
|
if (!data) {
|
|
return;
|
|
}
|
|
var options = column.optionList;
|
|
|
|
if (!options || !options.object) {
|
|
return;
|
|
}
|
|
if (angular.isArray(options.object)) {
|
|
source = options.object;
|
|
} else {
|
|
// Using $parse to evaluate the expression against the row object
|
|
// allows us to support filters like the ngRepeat directive does.
|
|
var paramObject = $parse(options.object)(data);
|
|
|
|
if (angular.isArray(paramObject)) {
|
|
if (propertyOnly) {
|
|
for (var i = 0, length = paramObject.length; i < length; i++) {
|
|
var item = paramObject[i][options.property];
|
|
|
|
if (item !== null && item !== undefined) {
|
|
source.push(item);
|
|
}
|
|
}
|
|
} else {
|
|
source = paramObject;
|
|
}
|
|
} else {
|
|
source = paramObject;
|
|
}
|
|
}
|
|
process(source);
|
|
};
|
|
}
|
|
};
|
|
}
|
|
autoCompleteFactory.$inject = ['$parse'];
|
|
|
|
angular.module('ngHandsontable.services').factory('autoCompleteFactory', autoCompleteFactory);
|
|
}());
|
|
|
|
(function() {
|
|
|
|
function hotRegisterer() {
|
|
var instances = {};
|
|
|
|
return {
|
|
getInstance: function(id) {
|
|
return instances[id];
|
|
},
|
|
|
|
registerInstance: function(id, instance) {
|
|
instances[id] = instance;
|
|
},
|
|
|
|
removeInstance: function(id) {
|
|
instances[id] = void 0;
|
|
}
|
|
};
|
|
}
|
|
hotRegisterer.$inject = [];
|
|
|
|
angular.module('ngHandsontable.services').factory('hotRegisterer', hotRegisterer);
|
|
}());
|
|
|
|
(function() {
|
|
|
|
function hyphenate(string) {
|
|
return string.replace(/[A-Z]/g, function(match) {
|
|
return ('-' + match.charAt(0).toLowerCase());
|
|
});
|
|
}
|
|
|
|
function camelCase(string) {
|
|
return string.replace(/-\D/g, function(match) {
|
|
return match.charAt(1).toUpperCase();
|
|
});
|
|
}
|
|
|
|
function ucFirst(string) {
|
|
return string.substr(0, 1).toUpperCase() + string.substr(1, string.length - 1);
|
|
}
|
|
|
|
function settingFactory(hotRegisterer) {
|
|
return {
|
|
containerClassName: 'handsontable-container',
|
|
|
|
/**
|
|
* Append handsontable container div and initialize handsontable instance inside element.
|
|
*
|
|
* @param {qLite} element
|
|
* @param {Object} htSettings
|
|
*/
|
|
initializeHandsontable: function(element, htSettings) {
|
|
var container = document.createElement('div'),
|
|
hot;
|
|
|
|
container.className = this.containerClassName;
|
|
|
|
if (htSettings.hotId) {
|
|
container.id = htSettings.hotId;
|
|
}
|
|
element[0].appendChild(container);
|
|
hot = new Handsontable(container, htSettings);
|
|
|
|
if (htSettings.hotId) {
|
|
hotRegisterer.registerInstance(htSettings.hotId, hot);
|
|
}
|
|
|
|
return hot;
|
|
},
|
|
|
|
/**
|
|
* Set new settings to handsontable instance.
|
|
*
|
|
* @param {Handsontable} instance
|
|
* @param {Object} settings
|
|
*/
|
|
updateHandsontableSettings: function(instance, settings) {
|
|
if (instance) {
|
|
instance.updateSettings(settings);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Render handsontable instance inside element.
|
|
*
|
|
* @param {Handsontable} instance
|
|
*/
|
|
renderHandsontable: function(instance) {
|
|
if (instance) {
|
|
instance.render();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Merge original handsontable settings with setting defined in scope.
|
|
*
|
|
* @param {Object} settings
|
|
* @param {Object} scope
|
|
* @returns {Object}
|
|
*/
|
|
mergeSettingsFromScope: function(settings, scope) {
|
|
var
|
|
scopeOptions = angular.extend({}, scope),
|
|
htOptions, i, length;
|
|
|
|
settings = settings || {};
|
|
angular.extend(scopeOptions, scope.settings || {});
|
|
htOptions = this.getAvailableSettings();
|
|
|
|
for (i = 0, length = htOptions.length; i < length; i++) {
|
|
if (typeof scopeOptions[htOptions[i]] !== 'undefined') {
|
|
settings[htOptions[i]] = scopeOptions[htOptions[i]];
|
|
}
|
|
}
|
|
|
|
return settings;
|
|
},
|
|
|
|
/**
|
|
* Merge original handsontable hooks with hooks defined in scope.
|
|
*
|
|
* @param {Object} settings
|
|
* @param {Object} scope
|
|
* @returns {Object}
|
|
*/
|
|
mergeHooksFromScope: function(settings, scope) {
|
|
var
|
|
scopeOptions = angular.extend({}, scope),
|
|
htHooks, i, length, attribute;
|
|
|
|
settings = settings || {};
|
|
angular.extend(scopeOptions, scope.settings || {});
|
|
htHooks = this.getAvailableHooks();
|
|
|
|
for (i = 0, length = htHooks.length; i < length; i++) {
|
|
attribute = 'on' + ucFirst(htHooks[i]);
|
|
|
|
if (typeof scopeOptions[htHooks[i]] === 'function' || typeof scopeOptions[attribute] === 'function') {
|
|
settings[htHooks[i]] = scopeOptions[htHooks[i]] || scopeOptions[attribute];
|
|
}
|
|
}
|
|
|
|
return settings;
|
|
},
|
|
|
|
/**
|
|
* Trim scope definition according to attrs object from directive.
|
|
*
|
|
* @param {Object} scopeDefinition
|
|
* @param {Object} attrs
|
|
* @returns {Object}
|
|
*/
|
|
trimScopeDefinitionAccordingToAttrs: function(scopeDefinition, attrs) {
|
|
for (var i in scopeDefinition) {
|
|
if (scopeDefinition.hasOwnProperty(i) && attrs[i] === void 0 &&
|
|
attrs[scopeDefinition[i].substr(1, scopeDefinition[i].length)] === void 0) {
|
|
delete scopeDefinition[i];
|
|
}
|
|
}
|
|
|
|
return scopeDefinition;
|
|
},
|
|
|
|
/**
|
|
* Get isolate scope definition for main handsontable directive.
|
|
*
|
|
* @return {Object}
|
|
*/
|
|
getTableScopeDefinition: function() {
|
|
var scopeDefinition = {};
|
|
|
|
this.applyAvailableSettingsScopeDef(scopeDefinition);
|
|
this.applyAvailableHooksScopeDef(scopeDefinition);
|
|
|
|
scopeDefinition.datarows = '=';
|
|
scopeDefinition.dataschema = '=';
|
|
scopeDefinition.observeDomVisibility = '=';
|
|
//scopeDefinition.settings = '=';
|
|
|
|
return scopeDefinition;
|
|
},
|
|
|
|
/**
|
|
* Get isolate scope definition for column directive.
|
|
*
|
|
* @return {Object}
|
|
*/
|
|
getColumnScopeDefinition: function() {
|
|
var scopeDefinition = {};
|
|
|
|
this.applyAvailableSettingsScopeDef(scopeDefinition);
|
|
scopeDefinition.data = '@';
|
|
|
|
return scopeDefinition;
|
|
},
|
|
|
|
/**
|
|
* Apply all available handsontable settings into object which represents scope definition.
|
|
*
|
|
* @param {Object} [scopeDefinition]
|
|
* @returns {Object}
|
|
*/
|
|
applyAvailableSettingsScopeDef: function(scopeDefinition) {
|
|
var options, i, length;
|
|
|
|
options = this.getAvailableSettings();
|
|
|
|
for (i = 0, length = options.length; i < length; i++) {
|
|
scopeDefinition[options[i]] = '=';
|
|
}
|
|
|
|
return scopeDefinition;
|
|
},
|
|
|
|
/**
|
|
* Apply all available handsontable hooks into object which represents scope definition.
|
|
*
|
|
* @param {Object} [scopeDefinition]
|
|
* @returns {Object}
|
|
*/
|
|
applyAvailableHooksScopeDef: function(scopeDefinition) {
|
|
var options, i, length;
|
|
|
|
options = this.getAvailableHooks();
|
|
|
|
for (i = 0, length = options.length; i < length; i++) {
|
|
scopeDefinition[options[i]] = '=on' + ucFirst(options[i]);
|
|
}
|
|
|
|
return scopeDefinition;
|
|
},
|
|
|
|
/**
|
|
* Get all available settings from handsontable, returns settings by default in camelCase mode.
|
|
*
|
|
* @param {Boolean} [hyphenateStyle=undefined] If `true` then returns options in hyphenate mode (eq. row-header)
|
|
* @returns {Array}
|
|
*/
|
|
getAvailableSettings: function(hyphenateStyle) {
|
|
var settings = Object.keys(Handsontable.DefaultSettings.prototype);
|
|
|
|
if (settings.indexOf('contextMenuCopyPaste') === -1) {
|
|
settings.push('contextMenuCopyPaste');
|
|
}
|
|
if (settings.indexOf('handsontable') === -1) {
|
|
settings.push('handsontable');
|
|
}
|
|
if (settings.indexOf('settings') >= 0) {
|
|
settings.splice(settings.indexOf('settings'), 1);
|
|
}
|
|
if (hyphenateStyle) {
|
|
settings = settings.map(hyphenate);
|
|
}
|
|
|
|
return settings;
|
|
},
|
|
|
|
/**
|
|
* Get all available hooks from handsontable, returns hooks by default in camelCase mode.
|
|
*
|
|
* @param {Boolean} [hyphenateStyle=undefined] If `true` then returns hooks in hyphenate mode (eq. on-after-init)
|
|
* @returns {Array}
|
|
*/
|
|
getAvailableHooks: function(hyphenateStyle) {
|
|
var settings = Handsontable.hooks.getRegistered();
|
|
|
|
if (hyphenateStyle) {
|
|
settings = settings.map(function(hook) {
|
|
return 'on-' + hyphenate(hook);
|
|
});
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
};
|
|
}
|
|
settingFactory.$inject = ['hotRegisterer'];
|
|
|
|
angular.module('ngHandsontable.services').factory('settingFactory', settingFactory);
|
|
}());
|
|
|
|
(function() {
|
|
/**
|
|
* Angular Handsontable directive for autocomplete settings
|
|
*/
|
|
function hotAutocomplete() {
|
|
return {
|
|
restrict: 'EA',
|
|
scope: true,
|
|
require: '^hotColumn',
|
|
link: function(scope, element, attrs, controllerInstance) {
|
|
var options = attrs.datarows;
|
|
|
|
controllerInstance.setColumnOptionList(options);
|
|
}
|
|
};
|
|
}
|
|
hotAutocomplete.$inject = [];
|
|
|
|
angular.module('ngHandsontable.directives').directive('hotAutocomplete', hotAutocomplete);
|
|
}());
|
|
|
|
(function() {
|
|
/**
|
|
* Angular Handsontable directive for single column settings
|
|
*/
|
|
function hotColumn(settingFactory) {
|
|
return {
|
|
restrict: 'EA',
|
|
require: '^hotTable',
|
|
scope: {},
|
|
controller: ['$scope', function($scope) {
|
|
this.setColumnOptionList = function(options) {
|
|
if (!$scope.column) {
|
|
$scope.column = {};
|
|
}
|
|
var optionList = {};
|
|
var match = options.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)\s*$/);
|
|
|
|
if (match) {
|
|
optionList.property = match[1];
|
|
optionList.object = match[2];
|
|
} else {
|
|
optionList.object = options.split(',');
|
|
}
|
|
$scope.column.optionList = optionList;
|
|
};
|
|
}],
|
|
compile: function(tElement, tAttrs) {
|
|
var _this = this;
|
|
|
|
this.scope = settingFactory.trimScopeDefinitionAccordingToAttrs(settingFactory.getColumnScopeDefinition(), tAttrs);
|
|
//this.$$isolateBindings = {};
|
|
|
|
angular.forEach(Object.keys(this.scope), function(key) {
|
|
_this.$$isolateBindings[key] = {
|
|
attrName: key,
|
|
collection: false,
|
|
mode: key === 'data' ? '@' : '=',
|
|
optional: false
|
|
};
|
|
});
|
|
|
|
return function(scope, element, attrs, controllerInstance) {
|
|
var column = {};
|
|
|
|
// Turn all attributes without value as `true` by default
|
|
angular.forEach(Object.keys(attrs), function(key) {
|
|
if (key.charAt(0) !== '$' && attrs[key] === '') {
|
|
column[key] = true;
|
|
}
|
|
});
|
|
settingFactory.mergeSettingsFromScope(column, scope);
|
|
|
|
if (!scope.column) {
|
|
scope.column = {};
|
|
}
|
|
angular.extend(scope.column, column);
|
|
controllerInstance.setColumnSetting(scope.column);
|
|
|
|
scope.$on('$destroy', function() {
|
|
controllerInstance.removeColumnSetting(scope.column);
|
|
});
|
|
};
|
|
}
|
|
};
|
|
}
|
|
hotColumn.$inject = ['settingFactory'];
|
|
|
|
angular.module('ngHandsontable.directives').directive('hotColumn', hotColumn);
|
|
}());
|
|
|
|
(function() {
|
|
/**
|
|
* Main Angular Handsontable directive
|
|
*/
|
|
function hotTable(settingFactory, autoCompleteFactory, $rootScope, $parse) {
|
|
return {
|
|
restrict: 'EA',
|
|
scope: {},
|
|
// for ng-repeat
|
|
priority: -400,
|
|
controller: ['$scope', function($scope) {
|
|
this.setColumnSetting = function(column) {
|
|
if (!$scope.htSettings) {
|
|
$scope.htSettings = {};
|
|
}
|
|
if (!$scope.htSettings.columns) {
|
|
$scope.htSettings.columns = [];
|
|
}
|
|
$scope.htSettings.columns.push(column);
|
|
settingFactory.updateHandsontableSettings($scope.hotInstance, $scope.htSettings);
|
|
};
|
|
this.removeColumnSetting = function(column) {
|
|
if ($scope.htSettings.columns.indexOf(column) > -1) {
|
|
$scope.htSettings.columns.splice($scope.htSettings.columns.indexOf(column), 1);
|
|
settingFactory.updateHandsontableSettings($scope.hotInstance, $scope.htSettings);
|
|
}
|
|
};
|
|
}],
|
|
compile: function(tElement, tAttrs) {
|
|
var _this = this,
|
|
bindingsKeys;
|
|
|
|
this.scope = settingFactory.trimScopeDefinitionAccordingToAttrs(settingFactory.getTableScopeDefinition(), tAttrs);
|
|
bindingsKeys = Object.keys(this.scope);
|
|
|
|
angular.forEach(bindingsKeys, function(key) {
|
|
var mode = _this.scope[key].charAt(0);
|
|
|
|
_this.$$isolateBindings[key] = {
|
|
attrName: _this.scope[key].length > 1 ? _this.scope[key].substr(1, _this.scope[key].length) : key,
|
|
collection: key === 'datarows',
|
|
mode: mode,
|
|
optional: false
|
|
};
|
|
});
|
|
|
|
return function(scope, element, attrs) {
|
|
scope.settings = $parse(attrs.settings)(scope.$parent);
|
|
|
|
if (!scope.htSettings) {
|
|
scope.htSettings = {};
|
|
}
|
|
// Turn all attributes without value as `true` by default
|
|
angular.forEach(Object.keys(attrs), function(key) {
|
|
if (key.charAt(0) !== '$' && attrs[key] === '') {
|
|
scope.htSettings[key] = true;
|
|
}
|
|
});
|
|
|
|
settingFactory.mergeSettingsFromScope(scope.htSettings, scope);
|
|
settingFactory.mergeHooksFromScope(scope.htSettings, scope);
|
|
|
|
if (!scope.htSettings.data) {
|
|
scope.htSettings.data = scope.datarows;
|
|
}
|
|
scope.htSettings.dataSchema = scope.dataschema;
|
|
scope.htSettings.hotId = attrs.hotId;
|
|
scope.htSettings.observeDOMVisibility = scope.observeDomVisibility;
|
|
|
|
if (scope.htSettings.columns) {
|
|
for (var i = 0, length = scope.htSettings.columns.length; i < length; i++) {
|
|
var column = scope.htSettings.columns[i];
|
|
|
|
if (column.type !== 'autocomplete') {
|
|
continue;
|
|
}
|
|
if (!column.optionList) {
|
|
continue;
|
|
}
|
|
if (typeof column.optionList === 'string') {
|
|
var optionList = {};
|
|
var match = column.optionList.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)\s*$/);
|
|
|
|
if (match) {
|
|
optionList.property = match[1];
|
|
optionList.object = match[2];
|
|
} else {
|
|
optionList.object = optionList;
|
|
}
|
|
column.optionList = optionList;
|
|
}
|
|
autoCompleteFactory.parseAutoComplete(column, scope.datarows, true);
|
|
}
|
|
}
|
|
var origAfterChange = scope.htSettings.afterChange;
|
|
|
|
scope.htSettings.afterChange = function() {
|
|
if (origAfterChange) {
|
|
origAfterChange.apply(this, arguments);
|
|
}
|
|
if (!$rootScope.$$phase) {
|
|
scope.$apply();
|
|
}
|
|
};
|
|
scope.hotInstance = settingFactory.initializeHandsontable(element, scope.htSettings);
|
|
|
|
// TODO: Add watch properties descriptor + needs perf test. Watch full equality vs toJson
|
|
angular.forEach(bindingsKeys, function(key) {
|
|
scope.$watch(key, function(newValue, oldValue) {
|
|
if (newValue === void 0) {
|
|
return;
|
|
}
|
|
if (key === 'datarows') {
|
|
// If reference to data rows is not changed then only re-render table
|
|
if (scope.hotInstance.getSettings().data === newValue) {
|
|
settingFactory.renderHandsontable(scope.hotInstance);
|
|
} else {
|
|
scope.hotInstance.loadData(newValue);
|
|
scope.htSettings.data = newValue;
|
|
}
|
|
} else if (newValue !== oldValue) {
|
|
scope.htSettings[key] = newValue;
|
|
settingFactory.updateHandsontableSettings(scope.hotInstance, scope.htSettings);
|
|
}
|
|
}, ['datarows', 'columns', 'rowHeights', 'colWidths', 'rowHeaders', 'colHeaders'].indexOf(key) >= 0);
|
|
});
|
|
|
|
/**
|
|
* Check for reference equality changes for datarows
|
|
* TODO: must the remaining bindingsKeys need to be added also if their reference changes
|
|
*/
|
|
scope.$watch('datarows', function(newValue) {
|
|
if (newValue === void 0) {
|
|
return;
|
|
}
|
|
if (scope.hotInstance.getSettings().data !== newValue) {
|
|
scope.hotInstance.loadData(newValue);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Check if data length has been changed
|
|
*/
|
|
scope.$watchCollection('datarows', function(newValue, oldValue) {
|
|
if (oldValue && oldValue.length === scope.htSettings.minSpareRows && newValue.length !== scope.htSettings.minSpareRows) {
|
|
scope.htSettings.data = scope.datarows;
|
|
settingFactory.updateHandsontableSettings(scope.hotInstance, scope.htSettings);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
};
|
|
}
|
|
hotTable.$inject = ['settingFactory', 'autoCompleteFactory', '$rootScope', '$parse'];
|
|
|
|
angular.module('ngHandsontable.directives').directive('hotTable', hotTable);
|
|
}());
|