├── .gitignore
├── Gemfile
├── public
├── favicon.ico
├── app
│ ├── stores
│ │ └── Users.js
│ ├── app.js
│ ├── views
│ │ ├── Viewport.js
│ │ ├── ErrorField.js
│ │ └── users
│ │ │ ├── List.js
│ │ │ └── Form.js
│ ├── models
│ │ └── User.js
│ └── controllers
│ │ └── Users.js
├── stylesheets
│ └── styles.css
└── index.html
├── config.ru
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | steps
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source :rubygems
2 |
3 | gem "rack"
4 | gem "serve"
5 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/senchalearn/Forms-demo/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | use Rack::Static, :urls => { "/" => "index.html" }, :root => "public"
2 | run Rack::Directory.new("public")
3 |
4 |
--------------------------------------------------------------------------------
/public/app/stores/Users.js:
--------------------------------------------------------------------------------
1 | App.stores.users = new Ext.data.Store({
2 | model: 'User',
3 | autoLoad: true
4 | });
5 |
--------------------------------------------------------------------------------
/public/stylesheets/styles.css:
--------------------------------------------------------------------------------
1 | .invalid-field .x-form-label { color: red; }
2 |
3 | .errorfield { font-size: 0.75em; }
4 | .errorfield ul { margin: 10px; }
5 |
--------------------------------------------------------------------------------
/public/app/app.js:
--------------------------------------------------------------------------------
1 | App = new Ext.Application({
2 | name: "App",
3 |
4 | launch: function() {
5 | this.views.viewport = new this.views.Viewport();
6 |
7 | this.views.usersList = this.views.viewport.down('#usersList');
8 | this.views.usersForm = this.views.viewport.down('#usersForm');
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/public/app/views/Viewport.js:
--------------------------------------------------------------------------------
1 | App.views.Viewport = Ext.extend(Ext.Panel, {
2 | fullscreen: true,
3 | layout: 'card',
4 |
5 | initComponent: function() {
6 | Ext.apply(this, {
7 | items: [
8 | { xtype: 'App.views.UsersList', id: 'usersList' },
9 | { xtype: 'App.views.UsersForm', id: 'usersForm' },
10 | ]
11 | });
12 | App.views.Viewport.superclass.initComponent.apply(this, arguments);
13 | },
14 |
15 | reveal: function(target) {
16 | var direction = (target === 'usersList') ? 'right' : 'left'
17 | this.setActiveItem(
18 | App.views[target],
19 | { type: 'slide', direction: direction }
20 | );
21 | }
22 | });
23 |
--------------------------------------------------------------------------------
/public/app/views/ErrorField.js:
--------------------------------------------------------------------------------
1 | App.views.ErrorField = Ext.extend(Ext.Component, {
2 |
3 | initComponent: function() {
4 | config = {
5 | xtype: 'component',
6 | id: this.fieldname + 'ErrorField',
7 | cls: 'errorfield',
8 | tpl: [
9 | '',
10 | ' ',
11 | ' ',
12 | ' - {field} {message}
',
13 | ' ',
14 | '
',
15 | ''
16 | ],
17 | hidden: true
18 | };
19 |
20 | Ext.apply(this, config);
21 | App.views.ErrorField.superclass.initComponent.call(this);
22 | },
23 |
24 | });
25 | Ext.reg('App.views.ErrorField', App.views.ErrorField);
26 |
--------------------------------------------------------------------------------
/public/app/models/User.js:
--------------------------------------------------------------------------------
1 | App.models.User = Ext.regModel('User', {
2 | fields: [
3 | {
4 | name: 'id',
5 | type: 'int'
6 | }, {
7 | name: 'name',
8 | type: 'string'
9 | }, {
10 | name: 'email',
11 | type: 'string'
12 | }, {
13 | name: 'phone',
14 | type: 'string'
15 | }
16 | ],
17 |
18 | validations: [
19 | {
20 | type: 'presence',
21 | name: 'name'
22 | }, {
23 | type: 'format',
24 | name: 'email',
25 | matcher: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
26 | message: 'must be a valid email'
27 | }
28 | ],
29 |
30 | proxy: {
31 | type: 'localstorage',
32 | id: 'sencha-users'
33 | }
34 | });
35 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sencha Touch
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/app/controllers/Users.js:
--------------------------------------------------------------------------------
1 | Ext.regController('Users', {
2 | store: App.stores.users,
3 |
4 | index: function() {
5 | App.views.viewport.reveal('usersList');
6 | },
7 |
8 | newForm: function() {
9 | var model = new App.models.User()
10 | App.views.usersForm.load(model);
11 | App.views.viewport.reveal('usersForm');
12 | },
13 |
14 | editForm: function(params) {
15 | var model = this.store.getAt(params.index);
16 | App.views.usersForm.load(model);
17 | App.views.viewport.reveal('usersForm');
18 | },
19 |
20 | save: function(params) {
21 | params.record.set(params.data);
22 | var errors = params.record.validate();
23 |
24 | if (errors.isValid()) {
25 | this.store.create(params.data);
26 | this.index();
27 | } else {
28 | params.form.showErrors(errors);
29 | }
30 | },
31 |
32 | update: function(params) {
33 | var tmpUser = new App.models.User(params.data),
34 | errors = tmpUser.validate()
35 |
36 | if (errors.isValid()) {
37 | params.record.set(params.data);
38 | params.record.save();
39 | this.index();
40 | } else {
41 | params.form.showErrors(errors);
42 | }
43 | },
44 |
45 | remove: function(params) {
46 | this.store.remove(params.record);
47 | this.store.sync();
48 | this.index();
49 | }
50 |
51 | });
52 |
--------------------------------------------------------------------------------
/public/app/views/users/List.js:
--------------------------------------------------------------------------------
1 | App.views.UsersList = Ext.extend(Ext.Panel, {
2 | initComponent: function(){
3 | var addButton, titlebar, list;
4 |
5 | addButton = {
6 | itemId: 'addButton',
7 | iconCls: 'add',
8 | iconMask: true,
9 | ui: 'plain',
10 | handler: this.onAddAction,
11 | scope: this
12 | };
13 |
14 | titlebar = {
15 | dock: 'top',
16 | xtype: 'toolbar',
17 | title: 'Users',
18 | items: [ { xtype: 'spacer' }, addButton ]
19 | };
20 |
21 | list = {
22 | xtype: 'list',
23 | itemTpl: '{name}',
24 | store: App.stores.users,
25 | listeners: {
26 | scope: this,
27 | itemtap: this.onItemtapAction
28 | }
29 | };
30 |
31 | Ext.apply(this, {
32 | html: 'placeholder',
33 | layout: 'fit',
34 | dockedItems: [titlebar],
35 | items: [list]
36 | });
37 |
38 | App.views.UsersList.superclass.initComponent.call(this);
39 | },
40 |
41 | onAddAction: function() {
42 | Ext.dispatch({
43 | controller: 'Users',
44 | action: 'newForm'
45 | });
46 | },
47 |
48 | onItemtapAction: function(list, index, item, e) {
49 | Ext.dispatch({
50 | controller: 'Users',
51 | action: 'editForm',
52 | index: index
53 | });
54 | }
55 | });
56 |
57 | Ext.reg('App.views.UsersList', App.views.UsersList);
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This Git repository includes all of the source code used in creating a tutorial about [working with Sencha Touch forms][t].
2 |
3 | The tutorial includes several checkpoints:
4 |
5 | * [Blank slate][00]
6 | * [Creating a user interface][01] ([view diff][00-01])
7 | * [Creating a form][02] ([view diff][01-02])
8 | * [Implementing the create action][03] ([view diff][02-03])
9 | * [Implementing the update action][04] ([view diff][03-04])
10 | * [Validating form fields][05] ([view diff][04-05])
11 | * [Implementing the delete action][06] ([view diff][05-06])
12 |
13 | ## Using this repository to follow the screencast
14 |
15 | First, you'll have to clone this repository:
16 |
17 | git clone git://github.com/senchalearn/Forms-demo.git
18 |
19 | Change into the directory:
20 |
21 | cd Forms-demo
22 |
23 | By default, the git clone command will only create the master branch locally. If you want to study the code at each checkpoint, you will have to fetch each of the other branches. You can do so by running the following:
24 |
25 | git checkout -b 00_blank_slate origin/00_blank_slate
26 | git checkout -b 01_user_interface origin/01_user_interface
27 | git checkout -b 02_create_new_form origin/02_create_new_form
28 | git checkout -b 03_implement_save_action origin/03_implement_save_action
29 | git checkout -b 04_create_edit_form origin/04_create_edit_form
30 | git checkout -b 05_validate_form_fields origin/05_validate_form_fields
31 | git checkout -b 06_implement_delete_action origin/06_implement_delete_action
32 |
33 | ## Live demo
34 |
35 | You can try out the demo here:
36 |
37 | * [http://sencha-forms.heroku.com/][t]
38 |
39 | [t]: http://www.sencha.com/learn/working-with-forms/
40 |
41 | [00]: https://github.com/senchalearn/Forms-demo/tree/00_blank_slate
42 | [01]: https://github.com/senchalearn/Forms-demo/tree/01_user_interface
43 | [02]: https://github.com/senchalearn/Forms-demo/tree/02_create_new_form
44 | [03]: https://github.com/senchalearn/Forms-demo/tree/03_implement_save_action
45 | [04]: https://github.com/senchalearn/Forms-demo/tree/04_create_edit_form
46 | [05]: https://github.com/senchalearn/Forms-demo/tree/05_validate_form_fields
47 | [06]: https://github.com/senchalearn/Forms-demo/tree/06_implement_delete_action
48 |
49 | [00-01]: https://github.com/senchalearn/Forms-demo/compare/00_blank_slate...01_user_interface
50 | [01-02]: https://github.com/senchalearn/Forms-demo/compare/01_user_interface...02_create_new_form
51 | [02-03]: https://github.com/senchalearn/Forms-demo/compare/02_create_new_form...03_implement_save_action
52 | [03-04]: https://github.com/senchalearn/Forms-demo/compare/03_implement_save_action...04_create_edit_form
53 | [04-05]: https://github.com/senchalearn/Forms-demo/compare/04_create_edit_form...05_validate_form_fields
54 | [05-06]: https://github.com/senchalearn/Forms-demo/compare/05_validate_form_fields...06_implement_delete_action
55 |
--------------------------------------------------------------------------------
/public/app/views/users/Form.js:
--------------------------------------------------------------------------------
1 | App.views.UsersForm = Ext.extend(Ext.form.FormPanel, {
2 | defaultInstructions: 'Please enter the information above.',
3 |
4 | initComponent: function(){
5 | var titlebar, cancelButton, buttonbar, saveButton, deleteButton, fields;
6 |
7 | cancelButton = {
8 | text: 'cancel',
9 | ui: 'back',
10 | handler: this.onCancelAction,
11 | scope: this
12 | };
13 |
14 | titlebar = {
15 | id: 'userFormTitlebar',
16 | xtype: 'toolbar',
17 | title: 'Create user',
18 | items: [ cancelButton ]
19 | };
20 |
21 | saveButton = {
22 | id: 'userFormSaveButton',
23 | text: 'save',
24 | ui: 'confirm',
25 | handler: this.onSaveAction,
26 | scope: this
27 | };
28 |
29 | deleteButton = {
30 | id: 'userFormDeleteButton',
31 | text: 'delete',
32 | ui: 'decline',
33 | handler: this.onDeleteAction,
34 | scope: this
35 | };
36 |
37 | buttonbar = {
38 | xtype: 'toolbar',
39 | dock: 'bottom',
40 | items: [deleteButton, {xtype: 'spacer'}, saveButton]
41 | };
42 |
43 | fields = {
44 | xtype: 'fieldset',
45 | id: 'userFormFieldset',
46 | title: 'User details',
47 | instructions: this.defaultInstructions,
48 | defaults: {
49 | xtype: 'textfield',
50 | labelAlign: 'left',
51 | labelWidth: '40%',
52 | required: false,
53 | useClearIcon: true,
54 | autoCapitalize : false
55 | },
56 | items: [
57 | {
58 | name : 'name',
59 | label: 'name',
60 | autoCapitalize : true
61 | },
62 | {
63 | xtype: 'App.views.ErrorField',
64 | fieldname: 'name',
65 | },
66 | {
67 | name: 'email',
68 | label: 'email',
69 | xtype: 'emailfield',
70 | },
71 | {
72 | xtype: 'App.views.ErrorField',
73 | fieldname: 'email',
74 | },
75 | {
76 | name: 'phone',
77 | label: 'phone',
78 | xtype: 'numberfield',
79 | },
80 | {
81 | xtype: 'App.views.ErrorField',
82 | fieldname: 'phone',
83 | },
84 | ]
85 | };
86 |
87 | Ext.apply(this, {
88 | scroll: 'vertical',
89 | dockedItems: [ titlebar, buttonbar ],
90 | items: [ fields ],
91 | listeners: {
92 | beforeactivate: function() {
93 | var deleteButton = this.down('#userFormDeleteButton'),
94 | saveButton = this.down('#userFormSaveButton'),
95 | titlebar = this.down('#userFormTitlebar'),
96 | model = this.getRecord();
97 |
98 | if (model.phantom) {
99 | titlebar.setTitle('Create user');
100 | saveButton.setText('create');
101 | deleteButton.hide();
102 | } else {
103 | titlebar.setTitle('Update user');
104 | saveButton.setText('update');
105 | deleteButton.show();
106 | }
107 | },
108 | deactivate: function() { this.resetForm() }
109 | }
110 | });
111 |
112 | App.views.UsersForm.superclass.initComponent.call(this);
113 | },
114 |
115 | onCancelAction: function() {
116 | Ext.dispatch({
117 | controller: 'Users',
118 | action: 'index'
119 | });
120 | },
121 |
122 | onSaveAction: function() {
123 | var model = this.getRecord();
124 |
125 | Ext.dispatch({
126 | controller: 'Users',
127 | action : (model.phantom ? 'save' : 'update'),
128 | data : this.getValues(),
129 | record : model,
130 | form : this
131 | });
132 | },
133 |
134 | onDeleteAction: function() {
135 | Ext.Msg.confirm("Delete this user?", "", function(answer) {
136 | if (answer === "yes") {
137 | Ext.dispatch({
138 | controller: 'Users',
139 | action : 'remove',
140 | record : this.getRecord()
141 | });
142 | }
143 | }, this);
144 | },
145 |
146 | showErrors: function(errors) {
147 | var fieldset = this.down('#userFormFieldset');
148 | this.fields.each(function(field) {
149 | var fieldErrors = errors.getByField(field.name);
150 |
151 | if (fieldErrors.length > 0) {
152 | var errorField = this.down('#'+field.name+'ErrorField');
153 | field.addCls('invalid-field');
154 | errorField.update(fieldErrors);
155 | errorField.show();
156 | } else {
157 | this.resetField(field);
158 | }
159 | }, this);
160 | fieldset.setInstructions("Please amend the flagged fields");
161 | },
162 |
163 | resetForm: function() {
164 | var fieldset = this.down('#userFormFieldset');
165 | this.fields.each(function(field) {
166 | this.resetField(field);
167 | }, this);
168 | fieldset.setInstructions(this.defaultInstructions);
169 | this.reset();
170 | },
171 |
172 | resetField: function(field) {
173 | var errorField = this.down('#'+field.name+'ErrorField');
174 | errorField.hide();
175 | field.removeCls('invalid-field');
176 | return errorField;
177 | }
178 | });
179 |
180 | Ext.reg('App.views.UsersForm', App.views.UsersForm);
181 |
--------------------------------------------------------------------------------