Hello,
I was already able to edit existing records a non-spatial table in my SDE database using FeatureLayer and FeatureTable following this example (Using FeatureTable (no map) | ArcGIS API for JavaScript) and using the editable property. I would now like to be able to insert (clicking a button would add a blank record at the bottom) and delete selected records from this table. Is this possible? I thought about using applyEdits but I understand it will only accept graphics as parameters (FeatureLayer | API Reference | ArcGIS API for JavaScript)
Thanks!
There is no built in functionality for this (at least at 3.16). I had a similar need and ended up extending the featureTable dijit and adding the attributeInspector dijit to create a new row.
There is a bunch of extra functionality that you likely won't need, but here is my extended dijit code:
define([ 'dijit/Dialog', 'dijit/form/Button', "dijit/Toolbar", "dojo/dom-class", 'dojo/dom-construct', 'dojo/_base/array', 'dojo/_base/declare', "dojo/string", 'dojo/_base/lang', 'dojo/on', "dojo/query", 'dojo/text!./templates/featureTableEditor.htm', 'esri/dijit/AttributeInspector', 'esri/dijit/FeatureTable', 'esri/layers/FeatureLayer', 'esri/tasks/query', "esri/graphic" ], function( Dialog, Button, Toolbar, domClass, domConstruct, array, declare, string, lang, on, dojoQuery, template, AttributeInspector, FeatureTable, FeatureLayer, Query, Graphic ) { return declare(FeatureTable, { constructor: function(params, srcNodeRef) { var _this = this; this.editableCustom = (params.editable === true) ? true : false; params.editable = false; this.newRecordAttributes = params.newRecordAttributes || {}; this.customHeaderText = params.customHeaderText || null; }, buildRendering: function () { this.templateString = template; this.inherited(arguments); }, postCreate: function() { var _this = this; console.debug('fte',this) this.on('load',this._postGridCreated); //add the main class domClass.add(this.domNode, "fwijitsFeatureTable"); this._i18nStrings.loadingData = "Loading Table Data..."; this._i18nStrings.gridHeader = '${gridTitle} (${featureSelectedCount}/${featureCount} selected)'; this.inherited(arguments); }, ///////////////// // Functions that override/enhance functionality in the ESRI featureTable _refreshGrid: function(ids){ //forces a refresh of the dataStore item var _this = this; this._createStoreFromDataQuery(); var listener = this.grid.on('dgrid-refresh-complete',function(){ listener.remove(); if (ids){ _this.selectRows(ids); } }); }, //override the applyEditsListener _applyEditsListener: function() { var listener = on(this.featureLayer, "edits-complete", lang.hitch(this, function(listener) { this.emit("edits-complete", listener); })); return listener; }, // End Functions that override/enhance functionality in the ESRI featureTable /////////////////////////////////////////////////////////////////////////// startup: function() { this.inherited(arguments); }, _updateGridHeaderText: function() { if (this._gridTitleNode){ this._gridTitleNode.innerHTML = string.substitute(this._i18nStrings.gridHeader, { gridTitle: this.customHeaderText || this.featureLayer.name || this._i18nStrings.untitled, featureCount: this.featureCount, featureSelectedCount: this._featureSelectedCount }); } }, _postGridCreated: function(){ var _this = this; //open the row editor on double click this.grid.on('.dgrid-row:dblclick', function(evt) { _this.grid.clearSelection(); var row = _this.grid.row(evt); _this.grid.select(row); _this._editSelection(); //_this._generateRowEditor(row.id); //_this._toggleLoadingIndicator('block'); }); // this._setupEditorButtons(); this.on('row-select',function(row){ _this._updateEditorButtons('select'); }); this.on('row-deselect',function(row){ _this._updateEditorButtons('deselect'); }); //this._addEditorButtons(); }, _editSelection: function(){ //currently only handles single row this._generateRowEditor(this.selectedRowIds[0]); this._toggleLoadingIndicator('block'); }, //_deleteSelection: function(){ // //}, _updateEditorButtons: function(type){ console.debug('running',this.selectedRowIds.length,type) var disableButtons = false; if (type == 'deselect' && this.selectedRowIds.length <= 1){ disableButtons = true; } this._editSelectionButton.setDisabled(disableButtons); //this._deleteSelectionButton.setDisabled(disableButtons); }, _addNew: function() { var _this = this; this._toggleLoadingIndicator('block'); this.featureLayer.applyEdits( [{ 'attributes': _this.newRecordAttributes }], null, null, function(results) { _this._refreshGrid([results[0].objectId]); _this._generateRowEditor(results[0].objectId); }, function(err) { alert('the add did not work'); } ); }, // _addEditorButtons: function() { // var _this = this; // // var addnewButtonContainer = domConstruct.create("div", { // class: 'esri-feature-table-menu-item', // style: 'float:left' // }, this.gridMenuAnchor.domNode, "after"); // // var addnewButton = new Button({ // label: "Add New" // }).placeAt(addnewButtonContainer); // // addnewButton.on('click', function() { // _this._toggleLoadingIndicator('block'); // _this.featureLayer.applyEdits( // [{ // 'attributes': _this.newRecordAttributes // }], // null, // null, // function(results) { // _this._refreshGrid([results[0].objectId]); // _this._generateRowEditor(results[0].objectId); // }, // function(err) { // alert('the add did not work'); // } // ); // }); // }, _generateRowEditor: function(rowId) { var _this = this; var changeToCommit = false; var editFields = []; array.forEach(this.featureLayer.fields, function(field) { if (_this.hiddenFields.indexOf(field.name) == -1) { editFields.push({ fieldName: field.name, isEditable: true, label: field.alias }); } }); var attInspector = new AttributeInspector({ layerInfos: [{ 'featureLayer': this.featureLayer, 'isEditable': true, 'fieldInfos': editFields, showDeleteButton: false // a custom delete button will be added later }] }, domConstruct.create("div")); domClass.add(attInspector.domNode, "fwijitsFeatureTableAttInspector"); console.debug(attInspector) if (_this.saveButton) { _this.saveButton.destroy(); } if (_this.deleteButton) { _this.deleteButton.destroy(); } if (this.attrInspDialog) { _this.attrInspDialog.destroy(); } var rowQuery = new Query(); rowQuery.where = "ObjectID =" + rowId; _this.featureLayer.selectFeatures(rowQuery, FeatureLayer.SELECTION_NEW, function(features) { var updateFeature = features[0]; _this.attrInspDialog = new Dialog({ title: "Update row", content: attInspector.domNode, style: "width: 350px", class:_this.css.dialog, onHide: function() { _this.saveButton.destroy(); _this._toggleLoadingIndicator(); //_this.clearSelection(); } }); _this.attrInspDialog.show(); //add a delete button _this.deleteButton = new Button({ label: "Delete" }, domConstruct.create("div")); domConstruct.place(_this.deleteButton.domNode, attInspector.deleteBtn.domNode, "after"); _this.deleteButton.on("click", function() { //alert('delete now') var apply = _this.featureLayer.applyEdits( null, null, [updateFeature] ); apply.then(function(res) { _this._refreshGrid(); }, function(err) { alert('delete failed'); }); _this.attrInspDialog.hide(); }); //add a save button _this.saveButton = new Button({ label: "Save", disabled: false }, domConstruct.create("div")); domConstruct.place(_this.saveButton.domNode, attInspector.deleteBtn.domNode, "after"); _this.saveButton.on("click", function() { _this.attrInspDialog.hide(); if (changeToCommit === true){ updateFeature.getLayer().applyEdits( null, [updateFeature], null, function(res) { _this._refreshGrid([rowId]); }, function(err) { alert('the update did not work'); } ); } }); attInspector.on("attribute-change", function(attr) { //_this.saveButton.setDisabled(false); changeToCommit = true; updateFeature.attributes[attr.fieldName] = attr.fieldValue; }); }); }, //this is a work in progress resizeColumnsToContent: function(newSize) { for (var i in this.grid.columns) { var col = this.grid.columns; if (col.hidden == false){ var fieldName = col.field; var fieldAlias = col.label; var newWidth = this._getColumnWidth(fieldName,fieldAlias); this.grid.resizeColumnWidth(col.id, newWidth); } } }, _stripTags: function(HTML) { var tmp = document.createElement("DIV"); tmp.innerHTML = HTML; return tmp.textContent || tmp.innerText; }, _replaceURLWithHTMLLinks: function(text) { var t = text + ""; var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; return t.replace(exp, "<a href='$1' target='blank'>View Page</a>"); }, _getColumnWidth: function(fieldName, fieldAlias, data) { var _this = this; var longestDataField = "-"; //fetch the field domain var domain = null; array.forEach(this.featureLayer.fields,function(fieldInfo){ if (fieldInfo.name == fieldName && fieldInfo.domain){ domain = fieldInfo.domain; } }); array.forEach(this.grid.store.data, function(item) { console.debug(' ',item) var cellData; if (item.attributes) { cellData = item.attributes[fieldName]; } else { cellData = item[fieldName]; } //handle domains if (domain && domain.codedValues){ var cellText = ''; array.forEach(domain.codedValues,function(codedValue){ if (codedValue.code == cellData){ cellText = codedValue.name; } }); if (cellText !== ''){ cellData = cellText; } } console.debug(' ',cellData) //account for empty cells cellData = (!cellData || cellData === null || cellData === ' ' || cellData === '') ? "-" : cellData; //Strip any html tags if (typeof cellData === 'string' && cellData.indexOf('<a') > -1) { cellData = _this._stripTags(cellData); } else if (typeof cellData === 'string' && cellData.indexOf('http') > -1 && cellData.indexOf('<a') == -1) { cellData = _this._stripTags(_this._replaceURLWithHTMLLinks(cellData)); } //check to see if the current fld is longer than previous flds if (cellData.length > longestDataField.length) { longestDataField = cellData; } }); //calculate header and cell string length var resizeFactor = 1.5; var maxColumnWidth = 30; var cellLen = longestDataField.length / resizeFactor; var hdrLen = fieldAlias.length <= 10 ? fieldAlias.length : fieldAlias.length / 1.3; var columnWidth = hdrLen > cellLen ? hdrLen : cellLen; columnWidth = columnWidth < 30 ? columnWidth : maxColumnWidth; return columnWidth*10; } }); });
The template:
<div class="${baseClass}"> <div id="${gridBorderContainerId}" class="esri-feature-table-border-container" data-dojo-attach-point="_gridBorderContainer" data-dojo-type="dijit.layout.BorderContainer" gutters="false"> <div id="${gridHeaderId}" class="esri-feature-table-content-pane esri-feature-table-menu" data-dojo-attach-point="_gridHeaderNode" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'top'"> <div class="esri-feature-table-menu-item esri-feature-table-loading-indicator" data-dojo-attach-point="_gridLoadingIndicatorNode" style="display:none;"></div> <div class="esri-feature-table-menu-item esri-feature-table-title" data-dojo-attach-point="_gridTitleNode"></div> <div class="esri-feature-table-menu-item esri-feature-table-closer-container"> <a class="esri-feature-table-closer toggleOpened" data-dojo-attach-point="tableCloseButton" href="JavaScript:void(0);" title="Hide Table"></a> </div> <div data-dojo-attach-point="_menuNode" class="esri-feature-table-menu-item esri-feature-table-menu-options"> <div id="${gridMenuNodeId}"></div> </div> </div> <div id="${gridContentPaneId}" class="esri-feature-table-content-pane" data-dojo-attach-point="_gridContentPane" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'center'"> <div id="${gridId}" class="esri-feature-table-grid" data-dojo-attach-point="_gridNode"></div> </div> <div class="esri-feature-table-editor-toolbar" data-dojo-attach-point="_gridEditToolbarPane" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'bottom'"> <div data-dojo-attach-point="_gridEditorToolbar" data-dojo-type="dijit/Toolbar"> <div data-dojo-type="dijit/form/Button" data-dojo-attach-event="onClick:_addNew" data-dojo-props="iconClass:'fwijitsFeatureTableEditorIcon fwijitsFeatureTableAdd', showLabel:false">Add</div> <div data-dojo-type="dijit/form/Button" data-dojo-attach-point="_editSelectionButton" data-dojo-attach-event="onClick:_editSelection" data-dojo-props="iconClass:'fwijitsFeatureTableEditorIcon fwijitsFeatureTableEdit', showLabel:false, disabled:true">Edit Selected</div> <!--div data-dojo-type="dijit/form/Button" data-dojo-attach-point="_deleteSelectionButton" data-dojo-attach-event="onClick:_deleteSelection" data-dojo-props="iconClass:'fwijitsFeatureTableEditorIcon fwijitsFeatureTableDelete', showLabel:false, disabled:true">Delete Selected</div--> </div> </div> </div> </div>
The css:
.dgrid { font-size: 9px; } .fwijitsFeatureTableMapSelectedRows .dgrid-selected { background-color: #00ffff; } .fwijitsFeatureTableMapSelectedRows .dgrid-row:hover { border: 2px solid #ff00ff; background-color: transparent; } .fwijitsFeatureTableMapSelectedRows .dgrid-selected:hover { border: 2px solid #ff00ff; background-color: #00ffff; } .fwijitsFeatureTableAttInspector .atiLayerName { display:none; } .esri-feature-table-editor-toolbar{ } .fwijitsFeatureTable .esri-feature-table-closer-container{ display:none; } .fwijitsFeatureTableAttInspector .atiAttributes .dijitButtonNode{ border-radius:0px !important; } .fwijitsFeatureTableAttInspector .atiField{ padding:0px !important; margin:2px; } .fwijitsFeatureTableEditorIcon { background-image: url('images/featureTableEditor/editorIconsEnabled.png'); /* editor icons sprite image for the enabled state */ background-repeat: no-repeat; width: 18px; height: 18px; text-align: center; } .dijitDisabled .fwijitsFeatureTableEditorIcon { background-image: url('images/featureTableEditor/editorIconsDisabled.png'); /* editor icons sprite image for the disabled state */ } .fwijitsFeatureTableAdd { background-position: 0; } .fwijitsFeatureTableEdit { background-position: -18px; } .fwijitsFeatureTableDelete { background-position: -36px; }
At the very least, the javascript code should give you a start.
I tried the refresh() function before, and it never refreshed the data as I wanted it, and it doesn't work in this instance either. I think for now I will work with the 3.16 version, and try to understand your code to migrate it to 3.17 and/or 4.0 in the future. I still need to understand some of the fundamentals such as extending classes as you did. Are there any resources on extending the ESRI API that you are aware of?
Looks like they changed some internal functions (anything function with an underscore should be referenced at your own risk) that I am referencing in the “_refreshGrid” function. Their version of the editor updates the grid, then applies the changes to the feature layer. My version edits the feature layer directly (using the attribute editor), then forces a refresh on the data. Based on the 3.17 documentation, it looks like they may have added a refresh function. You might try replacing this line:
this._createStoreFromDataQuery();
with:
this.refresh();
This does display the table fine, but then the add / update / delete operations do not work. For add it gives "the add did not work", for update it gives "the update did not work" (the popups) and for delete it gives this error on the console:
init.js:113 TypeError: this._createStoreFromDataQuery is not a function(…) "TypeError: this._createStoreFromDataQuery is not a function
at _refreshGrid (http://127.0.0.1:8020/extendFeatureTable/module/featureTableEditor.js:80:12)
at http://127.0.0.1:8020/extendFeatureTable/module/featureTableEditor.js:285:19
at a (https://js.arcgis.com/3.17/init.js:660:469)
at c (https://js.arcgis.com/3.17/init.js:103:393)
at d (https://js.arcgis.com/3.17/init.js:103:182)
at resolve.callback (https://js.arcgis.com/3.17/init.js:105:10)
at Object._resDfd (https://js.arcgis.com/3.17/init.js:661:122)
at _resolve (https://js.arcgis.com/3.17/init.js:1937:79)
at _editHandler (https://js.arcgis.com/3.17/init.js:1954:397)
at Object.load (https://js.arcgis.com/3.17/init.js:1912:132)"
It looks like they modified the template quite a bit at 3.17. Try using the following html. I haven't tried this myself yet.
<div> <div class="esri-feature-table-border-container" data-dojo-attach-point= "_gridBorderContainer" data-dojo-type="dijit.layout.BorderContainer" gutters="false"> <div class="esri-feature-table-content-pane esri-feature-table-menu" data-dojo-attach-point= "_gridHeaderNode" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'top'"> <div class="esri-feature-table-menu-item esri-feature-table-loading-indicator" data-dojo-attach-point= "_gridLoadingIndicatorNode" style="display:none;"></div> <div class="esri-feature-table-menu-item esri-feature-table-title" data-dojo-attach-point= "_gridTitleNode"></div> <div class="esri-feature-table-menu-item esri-feature-table-closer-container"></div> <div data-dojo-attach-point= "_menuNode" class="esri-feature-table-menu-item esri-feature-table-menu-options"> <div data-dojo-attach-point= "_gridMenuNode"></div> </div> </div> <div class="esri-feature-table-content-pane" data-dojo-attach-point= "_gridContentPane" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'center'"> <div class="esri-feature-table-grid" data-dojo-attach-point="_gridNode"></div> </div> <!-- custom editor tools section --> <div class="esri-feature-table-editor-toolbar" data-dojo-attach-point="_gridEditToolbarPane" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'bottom'"> <div data-dojo-attach-point="_gridEditorToolbar" data-dojo-type="dijit/Toolbar"> <div data-dojo-type="dijit/form/Button" data-dojo-attach-event="onClick:_addNew" data-dojo-props="iconClass:'fwijitsFeatureTableEditorIcon fwijitsFeatureTableAdd', showLabel:false">Add</div> <div data-dojo-type="dijit/form/Button" data-dojo-attach-point="_editSelectionButton" data-dojo-attach-event="onClick:_editSelection" data-dojo-props="iconClass:'fwijitsFeatureTableEditorIcon fwijitsFeatureTableEdit', showLabel:false, disabled:true">Edit Selected</div> <!--div data-dojo-type="dijit/form/Button" data-dojo-attach-point="_deleteSelectionButton" data-dojo-attach-event="onClick:_deleteSelection" data-dojo-props="iconClass:'fwijitsFeatureTableEditorIcon fwijitsFeatureTableDelete', showLabel:false, disabled:true">Delete Selected</div--> </div> </div> <!-- end custom editor tools section --> </div> </div>
Excellent. Glad to hear it works for you. Were you able to test it on 3.17?
Thanks. I got it working now. It looks and works awesome. By the way the main reason it didn't work was because I wasn't aware of "dojo/text!" requirement. I was putting "templates/featureTableEditor.html" for importing and the compiler was naturally looking for a JS file.
Thanks again!!
I don't think your too far off. Here are a couple things I notice right off the bat...
Thank you very much. I am trying to first use your code by integrating into the example at Using FeatureTable (no map) | ArcGIS API for JavaScript 3.17 , but so far I have been unsuccessful. I am probably doing some very basic mistakes but here is what I am doing. Here is my version of code (in a file text.html) and the folder structure.
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>FeatureTable without map</title> <link rel="stylesheet" href="https://community.esri.com//js.arcgis.com/3.17/dijit/themes/claro/claro.css"> <link rel="stylesheet" href="https://community.esri.com//js.arcgis.com/3.17/esri/css/esri.css"> <link rel="stylesheet" href="https://community.esri.com//js.arcgis.com/3.17/esri/css/esri.css"> <script src="//js.arcgis.com/3.17/"></script> <script type="text/javascript"> dojoConfig = { parseOnLoad: true, packages: [{ "name": "newModule", "location": location.pathname.replace(/\/[^/]+$/, "") + "/module" }] }; </script> <style> html, body, #myTableNode { width:100%; height:100%; margin:0; padding:0; } #top, #bot { margin: 0; padding: 0; } </style> <script> var map; //console.log(location.pathname.replace(/\/[^/]+$/, "") + "/module"); require([ "esri/layers/FeatureLayer", "esri/dijit/FeatureTable", "newModule/featureTableEditor", "dojo/dom", "dojo/parser", "dojo/ready", ], function ( FeatureLayer, FeatureTable,featureTableEditor, dom, parser, ready ) { parser.parse(); ready(function(){ // Create the feature layer var myFeatureLayer = new FeatureLayer("https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/california_census_blocks/FeatureServer/0", { mode: FeatureLayer.MODE_ONDEMAND, outFields: ["NAME","GEOID","MTFCC","ALAND","AWATER"], visible: true, id: "fLayer" }); myTable = new featureTableEditor({ featureLayer : myFeatureLayer, showGridMenu: false, hiddenFields: ["FID","C_Seq","Street"] // field that end-user can show, but is hidden on startup }, "myTableNode"); myTable.startup(); }); }); </script> </head> <body class="claro esri"> <div data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="design:'headline', gutters: false" style="width:100%; height:100%;"> <div id="top" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'top'"> <p><em>The FeatureTable dijit supports tables with lots of features, with the table growing as you scroll.</em></p> </div> <div id="bot" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'center'"> <div id="myTableNode"></div> </div> </div> </body> </html>
Have you tried creating a new Graphic object and not update the geometry?
I have this same question. I did not see any way to do this using the FeatureTable. Does anyone have any hints or suggestions as to accomplish adding a new row to the FeatureTable (and to delete an existing row)?
Thanks,
Abby
Signed in members can post, follow updates, and more. New here? Register a free account.
Find useful guides, FAQs, and documents to help you navigate and make the most of Esri Community.