/*! * ui-grid - v4.4.2 - 2018-03-20 * Copyright (c) 2018 ; License: MIT */ (function () { 'use strict'; /** * @ngdoc overview * @name ui.grid.importer * @description * * # ui.grid.importer * *
* gridOptions.importerProcessHeaders: function( headerArray ) { * var myHeaderColumns = []; * var thisCol; * headerArray.forEach( function( value, index ) { * thisCol = mySpecialLookupFunction( value ); * myHeaderColumns.push( thisCol.name ); * }); * * return myHeaderCols; * }) ** @param {Grid} grid the grid we're importing into * @param {array} headerArray an array of the text from the first row of the csv file, * which you need to match to column.names * @returns {array} array of matching column names, in the same order as the headerArray * */ gridOptions.importerProcessHeaders = gridOptions.importerProcessHeaders || service.processHeaders; /** * @ngdoc method * @name importerHeaderFilter * @methodOf ui.grid.importer.api:GridOptions * @description A callback function that will filter (usually translate) a single * header. Used when you want to match the passed in column names to the column * displayName after the header filter. * * Your callback routine needs to return the filtered header value. *
* gridOptions.importerHeaderFilter: function( displayName ) { * return $translate.instant( displayName ); * }) ** * or: *
* gridOptions.importerHeaderFilter: $translate.instant ** @param {string} displayName the displayName that we'd like to translate * @returns {string} the translated name * */ gridOptions.importerHeaderFilter = gridOptions.importerHeaderFilter || function( displayName ) { return displayName; }; /** * @ngdoc method * @name importerErrorCallback * @methodOf ui.grid.importer.api:GridOptions * @description A callback function that provides custom error handling, rather * than the standard grid behaviour of an alert box and a console message. You * might use this to internationalise the console log messages, or to write to a * custom logging routine that returned errors to the server. * *
* gridOptions.importerErrorCallback: function( grid, errorKey, consoleMessage, context ) { * myUserDisplayRoutine( errorKey ); * myLoggingRoutine( consoleMessage, context ); * }) ** @param {Grid} grid the grid we're importing into, may be useful if you're positioning messages * in some way * @param {string} errorKey one of the i18n keys the importer can return - importer.noHeaders, * importer.noObjects, importer.invalidCsv, importer.invalidJson, importer.jsonNotArray * @param {string} consoleMessage the English console message that importer would have written * @param {object} context the context data that importer would have appended to that console message, * often the file content itself or the element that is in error * */ if ( !gridOptions.importerErrorCallback || typeof(gridOptions.importerErrorCallback) !== 'function' ){ delete gridOptions.importerErrorCallback; } /** * @ngdoc method * @name importerDataAddCallback * @methodOf ui.grid.importer.api:GridOptions * @description A mandatory callback function that adds data to the source data array. The grid * generally doesn't add rows to the source data array, it is tidier to handle this through a user * callback. * *
* gridOptions.importerDataAddCallback: function( grid, newObjects ) { * $scope.myData = $scope.myData.concat( newObjects ); * }) ** @param {Grid} grid the grid we're importing into, may be useful in some way * @param {array} newObjects an array of new objects that you should add to your data * */ if ( gridOptions.enableImporter === true && !gridOptions.importerDataAddCallback ) { gridUtil.logError("You have not set an importerDataAddCallback, importer is disabled"); gridOptions.enableImporter = false; } /** * @ngdoc object * @name importerNewObject * @propertyOf ui.grid.importer.api:GridOptions * @description An object on which we call `new` to create each new row before inserting it into * the data array. Typically this would be a $resource entity, which means that if you're using * the rowEdit feature, you can directly call save on this entity when the save event is triggered. * * Defaults to a vanilla javascript object * * @example *
* gridOptions.importerNewObject = MyRes; ** */ /** * @ngdoc property * @propertyOf ui.grid.importer.api:GridOptions * @name importerShowMenu * @description Whether or not to show an item in the grid menu. Defaults to true. * */ gridOptions.importerShowMenu = gridOptions.importerShowMenu !== false; /** * @ngdoc method * @methodOf ui.grid.importer.api:GridOptions * @name importerObjectCallback * @description A callback that massages the data for each object. For example, * you might have data stored as a code value, but display the decode. This callback * can be used to change the decoded value back into a code. Defaults to doing nothing. * @param {Grid} grid in case you need it * @param {object} newObject the new object as importer has created it, modify it * then return the modified version * @returns {object} the modified object * @example *
* gridOptions.importerObjectCallback = function ( grid, newObject ) { * switch newObject.status { * case 'Active': * newObject.status = 1; * break; * case 'Inactive': * newObject.status = 2; * break; * } * return newObject; * }; **/ gridOptions.importerObjectCallback = gridOptions.importerObjectCallback || function( grid, newObject ) { return newObject; }; }, /** * @ngdoc function * @name addToMenu * @methodOf ui.grid.importer.service:uiGridImporterService * @description Adds import menu item to the grid menu, * allowing the user to request import of a file * @param {Grid} grid the grid into which data should be imported */ addToMenu: function ( grid ) { grid.api.core.addToGridMenu( grid, [ { title: i18nService.getSafeText('gridMenu.importerTitle'), order: 150 }, { templateUrl: 'ui-grid/importerMenuItemContainer', action: function ($event) { this.grid.api.importer.importAFile( grid ); }, order: 151 } ]); }, /** * @ngdoc function * @name importThisFile * @methodOf ui.grid.importer.service:uiGridImporterService * @description Imports the provided file into the grid using the file object * provided. Bypasses the grid menu * @param {Grid} grid the grid we're importing into * @param {File} fileObject the file we want to import, as returned from the File * javascript object */ importThisFile: function ( grid, fileObject ) { if (!fileObject){ gridUtil.logError( 'No file object provided to importThisFile, should be impossible, aborting'); return; } var reader = new FileReader(); switch ( fileObject.type ){ case 'application/json': reader.onload = service.importJsonClosure( grid ); break; default: reader.onload = service.importCsvClosure( grid ); break; } reader.readAsText( fileObject ); }, /** * @ngdoc function * @name importJson * @methodOf ui.grid.importer.service:uiGridImporterService * @description Creates a function that imports a json file into the grid. * The json data is imported into new objects of type `gridOptions.importerNewObject`, * and if the rowEdit feature is enabled the rows are marked as dirty * @param {Grid} grid the grid we want to import into * @param {FileObject} importFile the file that we want to import, as * a FileObject */ importJsonClosure: function( grid ) { return function( importFile ){ var newObjects = []; var newObject; var importArray = service.parseJson( grid, importFile ); if (importArray === null){ return; } importArray.forEach( function( value, index ) { newObject = service.newObject( grid ); angular.extend( newObject, value ); newObject = grid.options.importerObjectCallback( grid, newObject ); newObjects.push( newObject ); }); service.addObjects( grid, newObjects ); }; }, /** * @ngdoc function * @name parseJson * @methodOf ui.grid.importer.service:uiGridImporterService * @description Parses a json file, returns the parsed data. * Displays an error if file doesn't parse * @param {Grid} grid the grid that we want to import into * @param {FileObject} importFile the file that we want to import, as * a FileObject * @returns {array} array of objects from the imported json */ parseJson: function( grid, importFile ){ var loadedObjects; try { loadedObjects = JSON.parse( importFile.target.result ); } catch (e) { service.alertError( grid, 'importer.invalidJson', 'File could not be processed, is it valid json? Content was: ', importFile.target.result ); return; } if ( !Array.isArray( loadedObjects ) ){ service.alertError( grid, 'importer.jsonNotarray', 'Import failed, file is not an array, file was: ', importFile.target.result ); return []; } else { return loadedObjects; } }, /** * @ngdoc function * @name importCsvClosure * @methodOf ui.grid.importer.service:uiGridImporterService * @description Creates a function that imports a csv file into the grid * (allowing it to be used in the reader.onload event) * @param {Grid} grid the grid that we want to import into * @param {FileObject} importFile the file that we want to import, as * a file object */ importCsvClosure: function( grid ) { return function( importFile ){ var importArray = service.parseCsv( importFile ); if ( !importArray || importArray.length < 1 ){ service.alertError( grid, 'importer.invalidCsv', 'File could not be processed, is it valid csv? Content was: ', importFile.target.result ); return; } var newObjects = service.createCsvObjects( grid, importArray ); if ( !newObjects || newObjects.length === 0 ){ service.alertError( grid, 'importer.noObjects', 'Objects were not able to be derived, content was: ', importFile.target.result ); return; } service.addObjects( grid, newObjects ); }; }, /** * @ngdoc function * @name parseCsv * @methodOf ui.grid.importer.service:uiGridImporterService * @description Parses a csv file into an array of arrays, with the first * array being the headers, and the remaining arrays being the data. * The logic for this comes from https://github.com/thetalecrafter/excel.js/blob/master/src/csv.js, * which is noted as being under the MIT license. The code is modified to pass the jscs yoda condition * checker * @param {FileObject} importFile the file that we want to import, as a * file object */ parseCsv: function( importFile ) { var csv = importFile.target.result; // use the CSV-JS library to parse return CSV.parse(csv); }, /** * @ngdoc function * @name createCsvObjects * @methodOf ui.grid.importer.service:uiGridImporterService * @description Converts an array of arrays (representing the csv file) * into a set of objects. Uses the provided `gridOptions.importerNewObject` * to create the objects, and maps the header row into the individual columns * using either `gridOptions.importerProcessHeaders`, or by using a native method * of matching to either the displayName, column name or column field of * the columns in the column defs. The resulting objects will have attributes * that are named based on the column.field or column.name, in that order. * @param {Grid} grid the grid that we want to import into * @param {Array} importArray the data that we want to import, as an array */ createCsvObjects: function( grid, importArray ){ // pull off header row and turn into headers var headerMapping = grid.options.importerProcessHeaders( grid, importArray.shift() ); if ( !headerMapping || headerMapping.length === 0 ){ service.alertError( grid, 'importer.noHeaders', 'Column names could not be derived, content was: ', importArray ); return []; } var newObjects = []; var newObject; importArray.forEach( function( row, index ) { newObject = service.newObject( grid ); if ( row !== null ){ row.forEach( function( field, index ){ if ( headerMapping[index] !== null ){ newObject[ headerMapping[index] ] = field; } }); } newObject = grid.options.importerObjectCallback( grid, newObject ); newObjects.push( newObject ); }); return newObjects; }, /** * @ngdoc function * @name processHeaders * @methodOf ui.grid.importer.service:uiGridImporterService * @description Determines the columns that the header row from * a csv (or other) file represents. * @param {Grid} grid the grid we're importing into * @param {array} headerRow the header row that we wish to match against * the column definitions * @returns {array} an array of the attribute names that should be used * for that column, based on matching the headers or creating the headers * */ processHeaders: function( grid, headerRow ) { var headers = []; if ( !grid.options.columnDefs || grid.options.columnDefs.length === 0 ){ // we are going to create new columnDefs for all these columns, so just remove // spaces from the names to create fields headerRow.forEach( function( value, index ) { headers.push( value.replace( /[^0-9a-zA-Z\-_]/g, '_' ) ); }); return headers; } else { var lookupHash = service.flattenColumnDefs( grid, grid.options.columnDefs ); headerRow.forEach( function( value, index ) { if ( lookupHash[value] ) { headers.push( lookupHash[value] ); } else if ( lookupHash[ value.toLowerCase() ] ) { headers.push( lookupHash[ value.toLowerCase() ] ); } else { headers.push( null ); } }); return headers; } }, /** * @name flattenColumnDefs * @methodOf ui.grid.importer.service:uiGridImporterService * @description Runs through the column defs and creates a hash of * the displayName, name and field, and of each of those values forced to lower case, * with each pointing to the field or name * (whichever is present). Used to lookup column headers and decide what * attribute name to give to the resulting field. * @param {Grid} grid the grid we're importing into * @param {array} columnDefs the columnDefs that we should flatten * @returns {hash} the flattened version of the column def information, allowing * us to look up a value by `flattenedHash[ headerValue ]` */ flattenColumnDefs: function( grid, columnDefs ){ var flattenedHash = {}; columnDefs.forEach( function( columnDef, index) { if ( columnDef.name ){ flattenedHash[ columnDef.name ] = columnDef.field || columnDef.name; flattenedHash[ columnDef.name.toLowerCase() ] = columnDef.field || columnDef.name; } if ( columnDef.field ){ flattenedHash[ columnDef.field ] = columnDef.field || columnDef.name; flattenedHash[ columnDef.field.toLowerCase() ] = columnDef.field || columnDef.name; } if ( columnDef.displayName ){ flattenedHash[ columnDef.displayName ] = columnDef.field || columnDef.name; flattenedHash[ columnDef.displayName.toLowerCase() ] = columnDef.field || columnDef.name; } if ( columnDef.displayName && grid.options.importerHeaderFilter ){ flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName) ] = columnDef.field || columnDef.name; flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName).toLowerCase() ] = columnDef.field || columnDef.name; } }); return flattenedHash; }, /** * @ngdoc function * @name addObjects * @methodOf ui.grid.importer.service:uiGridImporterService * @description Inserts our new objects into the grid data, and * sets the rows to dirty if the rowEdit feature is being used * * Does this by registering a watch on dataChanges, which essentially * is waiting on the result of the grid data watch, and downstream processing. * * When the callback is called, it deregisters itself - we don't want to run * again next time data is added. * * If we never get called, we deregister on destroy. * * @param {Grid} grid the grid we're importing into * @param {array} newObjects the objects we want to insert into the grid data * @returns {object} the new object */ addObjects: function( grid, newObjects, $scope ){ if ( grid.api.rowEdit ){ var dataChangeDereg = grid.registerDataChangeCallback( function() { grid.api.rowEdit.setRowsDirty( newObjects ); dataChangeDereg(); }, [uiGridConstants.dataChange.ROW] ); grid.importer.$scope.$on( '$destroy', dataChangeDereg ); } grid.importer.$scope.$apply( grid.options.importerDataAddCallback( grid, newObjects ) ); }, /** * @ngdoc function * @name newObject * @methodOf ui.grid.importer.service:uiGridImporterService * @description Makes a new object based on `gridOptions.importerNewObject`, * or based on an empty object if not present * @param {Grid} grid the grid we're importing into * @returns {object} the new object */ newObject: function( grid ){ if ( typeof(grid.options) !== "undefined" && typeof(grid.options.importerNewObject) !== "undefined" ){ return new grid.options.importerNewObject(); } else { return {}; } }, /** * @ngdoc function * @name alertError * @methodOf ui.grid.importer.service:uiGridImporterService * @description Provides an internationalised user alert for the failure, * and logs a console message including diagnostic content. * Optionally, if the the `gridOptions.importerErrorCallback` routine * is defined, then calls that instead, allowing user specified error routines * @param {Grid} grid the grid we're importing into * @param {array} headerRow the header row that we wish to match against * the column definitions */ alertError: function( grid, alertI18nToken, consoleMessage, context ){ if ( grid.options.importerErrorCallback ){ grid.options.importerErrorCallback( grid, alertI18nToken, consoleMessage, context ); } else { $window.alert(i18nService.getSafeText( alertI18nToken )); gridUtil.logError(consoleMessage + context ); } } }; return service; } ]); /** * @ngdoc directive * @name ui.grid.importer.directive:uiGridImporter * @element div * @restrict A * * @description Adds importer features to grid * */ module.directive('uiGridImporter', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile', function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) { return { replace: true, priority: 0, require: '^uiGrid', scope: false, link: function ($scope, $elm, $attrs, uiGridCtrl) { uiGridImporterService.initializeGrid($scope, uiGridCtrl.grid); } }; } ]); /** * @ngdoc directive * @name ui.grid.importer.directive:uiGridImporterMenuItem * @element div * @restrict A * * @description Handles the processing from the importer menu item - once a file is * selected * */ module.directive('uiGridImporterMenuItem', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile', function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) { return { replace: true, priority: 0, require: '?^uiGrid', scope: false, templateUrl: 'ui-grid/importerMenuItem', link: function ($scope, $elm, $attrs, uiGridCtrl) { var grid; function handleFileSelect(event) { var target = event.srcElement || event.target; if (target && target.files && target.files.length === 1) { var fileObject = target.files[0]; // Define grid if the uiGrid controller is present if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) { grid = uiGridCtrl.grid; uiGridImporterService.importThisFile( grid, fileObject ); target.form.reset(); } else { gridUtil.logError('Could not import file because UI Grid was not found.'); } } } var fileChooser = $elm[0].querySelectorAll('.ui-grid-importer-file-chooser'); if ( fileChooser.length !== 1 ){ gridUtil.logError('Found > 1 or < 1 file choosers within the menu item, error, cannot continue'); } else { fileChooser[0].addEventListener('change', handleFileSelect, false); } } }; } ]); })();