By Alvin Alexander. Last updated: June 6, 2016
Sencha ExtJS - How to dynamically create a form textfield (Ext.form.field.Text)
Without much introduction, here’s a large block of Sencha ExtJS controller code. The code needs to be cleaned up, but at the moment it shows:
- How to dynamically create a Sencha ExtJS form textfield
- How to dynamically create a Sencha ExtJS form checkboxgroup
- How to dynamically add checkboxes to the checkboxgroup
- How to use Ext.Ajax.request POST and GET methods with JSON
- JSON encoding and decoding
- Much more, including how to strip blank spaces from a string, capitalize words
Here’s the source code:
Ext.define('Focus.controller.Projects', { extend: 'Ext.app.Controller', requires: [ 'VP.util.Utils' ], stores: [ 'Projects', 'Tasks' ], views: [ 'MainTabPanel', 'TaskListPanel' ], refs: [ { ref: 'mainTabPanel', selector: 'mainTabPanel' } ], // // TODO this code needs a lot of cleanup/refactoring // // note: this is called before the user logs in (currently) init: function(application) { this.control({ // lookup the 'My Stocks' button on the menu and add a click handler. // the name 'basic-panels' is the xtype declared in view.menu.Menu. // "basic-panels button#myStocks": { // click: this.onMyStocksButtonClick // }, // the MainTabPanel is rendered when the MainViewport is rendered // by the Login controller. "mainTabPanel": { render: this.onMainTabPanelRender, afterrender: this.onMainTabPanelAfterRender, tabchange: this.onMainTabPanelTabChange, }, "taskListPanel": { activate: this.onTaskListPanelActivate, afterrender: this.onTaskListPanelAfterRender, beforerender: this.onTaskListPanelBeforeRender, beforestaterestore: this.onTaskListPanelBeforeStateRestore, enable: this.onTaskListPanelEnable, render: this.onTaskListPanelRender, show: this.onTaskListPanelShow, staterestore: this.onTaskListPanelStateRestore, } }); }, onTaskListPanelActivate: function(panel, options) { // console.log("ENTERED onTaskListPanelActivate"); // // VP.util.Utils.dumpObject(panel); // if ('null' == panel) { // console.log(' PANEL WAS NULL'); // } // //var textfield = panel.down('#taskTextfield'); // //var textfield = panel.down('textfield[itemId=taskTextfield]'); // //var textfield = Ext.getCmp('textfield[name=taskTextfield]'); // //var textfield = panel.getComponent('taskTextfield'); // var textfield = Ext.ComponentQuery.query('#taskTextfield').el; // console.log(' AFTER GETTING THE TEXTFIELD'); // console.log(textfield); // if (null === textfield) { // console.log(' TEXTFIELD WAS NULL'); // } // //textfield.focus(false, 200); }, onTaskListPanelAfterRender: function(panel, options) { console.log("ENTERED onTaskListPanelAfterRender"); }, onTaskListPanelBeforeRender: function() { console.log("ENTERED onTaskListPanelBeforeRender"); }, onTaskListPanelBeforeStateRestore: function() { console.log("ENTERED onTaskListPanelBeforeStateRestore"); }, onTaskListPanelEnable: function() { console.log("ENTERED onTaskListPanelEnable"); }, onTaskListPanelRender: function() { console.log("ENTERED onTaskListPanelRender"); }, onTaskListPanelShow: function() { console.log("ENTERED onTaskListPanelShow"); }, onTaskListPanelStateRestore: function() { console.log("ENTERED onTaskListPanelStateRestore"); }, onLaunch: function() { console.log('ENTERED onLaunch'); // i don't know why, but this needs to be here for the store // to *really* be loaded }, // info on tab panel listeners: // http://blog.jardalu.com/2013/6/10/simple-tab-dynamic-content-extjs-sencha // http://goo.gl/llbcqn (long appfoundation.com url) onMainTabPanelTabChange: function(mainTabPanel, panel) { // panel (title: Finance; alias: widget.finance, etc.) console.log(panel.title); this.handleTabChange(panel.title, panel); }, addTextfieldToPanel: function(textField, panel) { this.addTextfieldListener(textField); panel.add(textField); }, // store loading urls: // http://www.curiousm.com/labs/2012/11/13/sencha-touch-2-dynamically-loading-the-store-of-a-list-and-asking-the-server-for-data-by-parameter/ handleTabChange: function(tabName, panel) { console.log('ENTERED handleTabChange'); var me = this; // need this to access `this` inside `each` loop below if (!Ext.getStore('Tasks')) { console.log('handleTabChange: Tasks store did not exist'); Ext.create('Tasks'); } // STORE: http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.data.Store //Ext.getStore('Tasks').load(); var tasksStore = this.getTasksStore(); //var tasksStore = Ext.getStore('Tasks'); // TODO probably want a urlencoded tabname here // these add the params as cgi parameters to the GET url tasksStore.getProxy().extraParams.projectId = me.getProjectId(tabName); //tasksStore.getProxy().extraParams.projectName = tabName; // it's important to do these actions inside the load() method, // because the load process can fail, and you need to handle that failure tasksStore.load({ callback: function(records, operation, success) { console.log('tasksStore.load called'); console.log(' success: ' + success); // clear the panel panel.removeAll(); // add the textfield me.addTextfieldToPanel(me.createTextField(), panel); // add hidden fields with the projectId and status. // TODO can handle the status as a simple form param panel.add(me.createHiddenProjectIdField(tabName)); panel.add(me.createHiddenStatusField()); if (success == true) { // MAKE THE CHECKBOXES var groupItemId = me.makeGroupItemIdName(tabName); var group = me.createCheckboxGroup(groupItemId); //panel.body.update(group); var count = 1; // TODO i don't know how to reference a method in this class/object // inside a block like this; a 'this' reference does not work by itself; // if i remember right, i need to pass something else to the function. console.log('ABOUT TO LOOP OVER TASK STORE ITEMS'); console.log('TASK STORE COUNT: ' + tasksStore.count()); tasksStore.each(function(record) { var task = record.data.description; console.log('TASK: ' + task); var checkbox = me.createCheckbox(task, count++); me.addCheckboxToGroup(group, checkbox); }); panel.add(group); panel.doLayout(); } else { // the store didn't load, anything to do? } } // scope: this, }); console.log('COUNT = ' + tasksStore.count()); }, // get the projectId from the projectName getProjectId: function(projectName) { var projectsStore = this.getProjectsStore(); var project = projectsStore.findRecord('name', projectName); return project.get('id'); }, // each checkbox group must have a different itemId. make the itemId from the // projectName, which must be unique per user. makeGroupItemIdName: function(projectName) { return VP.util.Utils.removeSpaces(VP.util.Utils.capitalizeEachWord(projectName)); }, createLegalIdNameFromString: function(s) { var x = VP.util.Utils.capitalizeEachWord(s); var y = VP.util.Utils.stripNonWordCharacters(x); return VP.util.Utils.removeSpaces(y); }, createHiddenProjectIdField: function(projectName) { return Ext.form.Field({ xtype: 'hiddenfield', name: 'projectId', value: this.getProjectId(projectName) }); }, createHiddenStatusField: function() { return Ext.form.Field({ xtype: 'hiddenfield', name: 'status', value: 'c' }); }, createTextField: function() { return Ext.create('Ext.form.field.Text', { fieldLabel: 'Task:', name: 'task', itemId: 'taskTextfield', autofocus: true, enableKeyEvents: true, labelAlign: 'left', labelWidth: 50, labelStyle: 'font-size: 16px;', width: 500, listeners: { // this works, putting focus in the textfield afterrender: function(field) { field.focus(false, 250); }, // ADDOption1 specialkey: function(field, event, options) { console.log('ENTERED specialkey'); if (event.getKey() == event.ENTER) { // var saveBtn = field.up('researchLinkForm').down('button#save'); // saveBtn.fireEvent('click', saveBtn, event, options); } } } }); }, // ADDOption2 addTextfieldListener: function(textfield) { var me = this; textfield.on('keyup', function(field, event, options) { if (event.getCharCode() === event.ENTER) { var formPanel = textfield.up('form'); var tasksStore = me.getTasksStore(); // if the form is valid, send the data if (formPanel.getForm().isValid()) { Ext.Ajax.request({ url: 'server/tasks/add', method: 'POST', headers: { 'Content-Type': 'application/json' }, params : Ext.JSON.encode(formPanel.getValues()), success: function(conn, response, options, eOpts) { var result = Packt.util.Util.decodeJSON(conn.responseText); if (result.success) { Packt.util.Alert.msg('Success!', 'Task was saved.'); // TODO do whatever is needed to add the checkbox to the list of checkboxes // TODO should also sync up the store // tasksStore.load(); var description = textfield.getValue(); var checkbox = me.createCheckbox(description, me.createLegalIdNameFromString(description)); // get the checkboxgroup and add the checkbox var group = formPanel.down('checkboxgroup'); me.insertCheckboxIntoGroup(group, checkbox); textfield.setValue(); //clear formPanel.doLayout(); } else { Packt.util.Util.showErrorMsg(result.msg); } }, failure: function(conn, response, options, eOpts) { // TODO get the 'msg' from the json and display it Packt.util.Util.showErrorMsg(conn.responseText); } }); } } }); }, createCheckboxGroup: function(nameOfId) { var grp = new Ext.form.CheckboxGroup({ //fieldLabel: 'CheckboxGroup', //hideLabel: true, columns: 1, id: nameOfId, itemId: nameOfId, formBind: true, // NIGHT listeners: { // may only be available after the 'load' event; see // http://pritomkumar.blogspot.com/2013/08/extjs-add-checkbox-group-to-panel-on-fly.html check: { element: 'el', //bind to the underlying el property on the panel foo: function (clickedObject, isChecked){ console.log(clickedObject); console.log(isChecked); console.log("CHECKBOXGROUP: Check box check status changed."); // var group = Ext.getCmp (nameOfId); // var length = group.items.getCount (); // var isCheck = group.items.items[0].checked; } }, change: { element: 'el', //bind to the underlying el property on the panel foo: function (field, newVal, oldVal){ console.log('CHECKBOXGROUP: CHANGE!'); console.log(field); } }, // works afterrender: function(foo) { console.log('afterrender'); }, // change: { // element: 'el', //bind to the underlying el property on the panel // fn: function(cb, checked) { // //Ext.getCmp('myTextField').setDisabled(!checked); // Ext.Msg.alert('You changed something'); // console.log('CHECKED: ' + checked); // }, // fn2: function(cb, newVal, oldVal) { // //Ext.getCmp('myTextField').setDisabled(!checked); // console.log('CHECKED: ' + newVal); // } // }, click: { element: 'el', //bind to the underlying el property on the panel fn: function(){ //Ext.Msg.alert('You clicked something'); console.log('CLICKED: (something)'); } } } }); return grp; }, // TODO add a listener to each box createCheckbox: function(taskName, uniqueIdentifier) { var me = this; var checkboxFieldClass = 'checkboxFieldClass'; var checkbox = new Ext.form.field.Checkbox({ boxLabel: ' <a href="#" class="taskName">' + taskName + '</a>', boxLabelCls: checkboxFieldClass, value: taskName, name: 'task', //itemId: me.createLegalIdNameFromString(taskName) + '_box_' + uniqueIdentifier, itemId: 'box' + uniqueIdentifier, inputValue: '1', // TODO checked: false, // found: http://stackoverflow.com/questions/15160466/enable-disable-text-field-on-checkbox-selection-extjs listeners: { // NIGHT: this really affects `this.boxLabel` //scope: this, change: function(checkbox, newValue, oldValue, eOpts) { if (newValue === true) { // // // TODO leaving off here // // console.log('TRYING TO DUMP CHECKBOX'); // VP.util.Utils.dumpObject(checkbox); console.log('this.boxLabel: ' + this.boxLabel); me.handleCheckboxClickedEvent(checkbox, this.boxLabel); } }, render: function(component) { component.getEl().on('click', function(event) { //VP.util.Utils.dumpObject(component.getEl().down('.boxLabelCls').dom.innerText); event.stopEvent(); var taskText = component.getEl().down('.'+checkboxFieldClass).dom.innerText; // a hack to figure out whether the user clicked the hyperlink or checkbox var objectType = Object.prototype.toString.call(event.target); if (objectType.indexOf("HTMLAnchorElement") > -1) { // user clicked on the hyperlink Ext.Msg.alert('Now Working On', taskText); } else { // user clicked the checkbox (HTMLInputElement) } }); } } }); return checkbox; }, // expects something like '<a href="#" class="taskName">Get tabs working</a>' getTextFromHyperlink: function(linkText) { return linkBody = linkText.match(/<a [^>]+>([^<]+)<\/a>/)[1]; // returns an array }, handleCheckboxClickedEvent: function(checkbox, checkboxHyperlinkText) { // NIGHT var linkText = this.getTextFromHyperlink(checkboxHyperlinkText); // var linkText = 'caca'; var formPanel = checkbox.up('form'); //VP.util.Utils.dumpObject(formPanel); console.log('formPanel: ' + formPanel); console.log(formPanel.getForm().getValues()); console.log('projectId: ' + formPanel.getForm().findField('projectId').getSubmitValue()); checkbox.hide(); // TODO remove from store Ext.Ajax.request({ url: 'server/tasks/updateStatus', method: 'POST', headers: { 'Content-Type': 'application/json' }, // formPanel.getForm().findField('NamePropertyValue').getSubmitValue( params: Ext.JSON.encode({ // "projectId" : formPanel.projectId.getValue(), "projectId" : formPanel.getForm().findField('projectId').getSubmitValue(), "task": linkText, "status": "f" }), success: function(conn, response, options, eOpts) { var result = Packt.util.Util.decodeJSON(conn.responseText); if (result.success) { Packt.util.Alert.msg('Success!', 'Task was removed from the server.'); // formPanel.doLayout(); } else { Packt.util.Util.showErrorMsg(result.msg); } }, failure: function(conn, response, options, eOpts) { // TODO get the 'msg' from the json and display it Packt.util.Util.showErrorMsg(conn.responseText); } }); }, insertCheckboxIntoGroup: function(group, checkbox) { group.items.insert(0, checkbox); }, addCheckboxToGroup: function(group, checkbox) { //var col = group.panel.items.get(group.items.getCount() % group.panel.items.getCount()); // OLD, WORKED (LATE NIGHT) group.items.add(checkbox); group.add(checkbox); //col.add(checkbox); //group.panel.doLayout(); }, // Usage: var string2 = stringWithoutSpaces(string1); stringWithoutSpaces: function(string) { // str.replace(/\s/g, ''); return string.replace(/ /g,''); }, capitalizeAllWords: function(str) { return str.replace(/\w\S*/g, function(txt){ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); }, /** * Handle the rendering of the MainTabPanel. * As it's rendered, dynamically add one tab for each project. * from 'mastering', p. 109 */ onMainTabPanelRender: function(component, options) { console.log('ENTERED onMainTabPanelRender, trying to get ProjectsStore'); var mainTabPanel = this.getMainTabPanel(); //var tabsArray = new Array(); var projectsStore = this.getProjectsStore(); projectsStore.load({ callback: function(records, operation, success) { console.log('projectsStore.load called'); console.log(' success: ' + success); if (success == true) { projectsStore.each(function(record) { //tabsArray.push(record.data.name); var tab = mainTabPanel.add({ xtype: 'taskListPanel', closable: false, title: record.data.name, // the text on the tab alias: 'widget.' + record.data.name.toLowerCase(), iconCls: record.data.name.toLowerCase(), // renders 'class="finance"' listeners : { click: function () { console.log("YOU CLICKED A TAB !!!"); }, // beforeclose : function(tab) { // tab.removeAll(); // tab.destroy(); // return true; // } } }); }); } else { console.log(' success was false (bad)'); } } }); mainTabPanel.setActiveTab(0); mainTabPanel.doLayout(); // TODO i may need to create these tabs as an array so i can // access them, and also know which one is in the foreground }, onMainTabPanelAfterRender: function(tabPanel, options) { console.log('ENTERED onMainTabPanelAfterRender'); tabPanel.setActiveTab(0); } });