/** * ng-handsontable 0.13.0 * * Copyright 2012-2015 Marcin Warpechowski * Copyright 2015 Handsoncode sp. z o.o. * 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); }());