skozlov-netzke-basepack 0.1.1.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. data/.autotest +1 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +2 -19
  4. data/README.rdoc +87 -0
  5. data/Rakefile +28 -12
  6. data/TODO.rdoc +7 -0
  7. data/VERSION +1 -0
  8. data/autotest/discover.rb +3 -0
  9. data/init.rb +0 -1
  10. data/javascripts/basepack.js +839 -49
  11. data/lib/app/models/netzke_auto_column.rb +56 -0
  12. data/lib/netzke/accordion_panel.rb +113 -0
  13. data/lib/netzke/active_record/basepack.rb +104 -0
  14. data/lib/netzke/active_record/data_accessor.rb +21 -0
  15. data/lib/netzke/basic_app.rb +325 -0
  16. data/lib/netzke/border_layout_panel.rb +128 -0
  17. data/lib/netzke/configuration_panel.rb +24 -0
  18. data/lib/netzke/data_accessor.rb +71 -0
  19. data/lib/netzke/ext.rb +6 -0
  20. data/lib/netzke/field_model.rb +131 -0
  21. data/lib/netzke/fields_configurator.rb +95 -0
  22. data/lib/netzke/form_panel.rb +214 -0
  23. data/lib/netzke/form_panel_api.rb +74 -0
  24. data/lib/netzke/form_panel_extras/javascripts/xcheckbox.js +82 -0
  25. data/lib/netzke/form_panel_js.rb +129 -0
  26. data/lib/netzke/grid_panel.rb +442 -0
  27. data/lib/netzke/grid_panel_api.rb +352 -0
  28. data/lib/netzke/grid_panel_extras/javascripts/check-column.js +33 -0
  29. data/{javascripts → lib/netzke/grid_panel_extras/javascripts}/filters.js +0 -0
  30. data/lib/netzke/grid_panel_extras/javascripts/rows-dd.js +280 -0
  31. data/lib/netzke/grid_panel_js.rb +721 -0
  32. data/lib/netzke/panel.rb +13 -0
  33. data/lib/netzke/plugins/configuration_tool.rb +121 -0
  34. data/lib/netzke/property_editor.rb +105 -0
  35. data/lib/netzke/property_editor_extras/helper_model.rb +126 -0
  36. data/lib/netzke/search_panel.rb +62 -0
  37. data/lib/netzke/tab_panel.rb +160 -0
  38. data/lib/netzke/table_editor.rb +118 -0
  39. data/lib/netzke/tree_panel.rb +73 -0
  40. data/lib/netzke/wrapper.rb +42 -0
  41. data/lib/netzke-basepack.rb +9 -15
  42. data/netzke-basepack.gemspec +147 -20
  43. data/stylesheets/basepack.css +26 -0
  44. data/test/app_root/app/models/book.rb +1 -1
  45. data/test/app_root/config/environment.rb +1 -0
  46. data/test/app_root/db/migrate/20081222033440_create_genres.rb +1 -0
  47. data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +1 -1
  48. data/test/app_root/db/migrate/20090102223630_create_netzke_layouts.rb +14 -0
  49. data/test/app_root/vendor/plugins/acts_as_list/README +23 -0
  50. data/test/app_root/vendor/plugins/acts_as_list/init.rb +3 -0
  51. data/test/app_root/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
  52. data/test/test_helper.rb +1 -2
  53. data/test/unit/accordion_panel_test.rb +20 -0
  54. data/test/unit/active_record_basepack_test.rb +54 -0
  55. data/test/unit/grid_panel_test.rb +43 -0
  56. data/test/unit/helper_model_test.rb +30 -0
  57. data/test/unit/netzke_basepack_test.rb +4 -0
  58. data/test/unit/tab_panel_test.rb +21 -0
  59. metadata +96 -72
  60. data/CHANGELOG +0 -14
  61. data/Manifest +0 -65
  62. data/README.mdown +0 -18
  63. data/generators/netzke_basepack/USAGE +0 -8
  64. data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -8
  65. data/generators/netzke_basepack/netzke_grid_generator.rb +0 -7
  66. data/generators/netzke_basepack/templates/create_netzke_grid_columns.rb +0 -21
  67. data/lib/app/models/netzke_grid_column.rb +0 -23
  68. data/lib/netzke/accordion.rb +0 -11
  69. data/lib/netzke/ar_ext.rb +0 -163
  70. data/lib/netzke/column.rb +0 -43
  71. data/lib/netzke/container.rb +0 -81
  72. data/lib/netzke/grid.rb +0 -120
  73. data/lib/netzke/grid_interface.rb +0 -156
  74. data/lib/netzke/grid_js_builder.rb +0 -276
  75. data/lib/netzke/preference_grid.rb +0 -43
  76. data/lib/netzke/properties_tool.rb +0 -66
  77. data/lib/netzke/property_grid.rb +0 -60
  78. data/test/ar_ext_test.rb +0 -39
  79. data/test/column_test.rb +0 -27
  80. data/test/grid_test.rb +0 -43
  81. data/test/netzke_basepack_test.rb +0 -8
data/.autotest ADDED
@@ -0,0 +1 @@
1
+ require 'autotest/redgreen'
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ *.log
3
+ pkg
4
+ doc
5
+ test/app_root/vendor/plugins/netzke-core
data/LICENSE CHANGED
@@ -1,20 +1,3 @@
1
- Copyright (c) 2008 Sergei Kozlov
1
+ Copyright (c) 2008-2009 Sergei Kozlov
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3
+ GNU GPL license v3
data/README.rdoc ADDED
@@ -0,0 +1,87 @@
1
+ = netzke-basepack
2
+ A pack of basic Rails/ExtJS widgets as a part of the Netzke framework. Live demo/tutorials on http://blog.writelesscode.com. Introduction to the Netzke framework: http://github.com/skozlov/netzke/tree/master
3
+
4
+ Note that if you would like to modify this code or experiment with it, you may be better off cloning this project into your app's vendor/plugin directory - it will then behave as a Rails plugin.
5
+
6
+ = Prerequisites
7
+ 1. Rails >= 2.2
8
+ 2. Ext JS >= 2.0: its root *must* be accessible as RAILS_ROOT/public/extjs. You may symlink your Ext JS library here like this (from your app folder):
9
+ cd public && ln -s ~/Developer/extjs/ext-2.2 extjs
10
+
11
+ 3. acts_as_list plugin must be installed:
12
+ ./script/plugin install git://github.com/rails/acts_as_list.git
13
+
14
+ = Installation
15
+ Install the gem:
16
+ gem install skozlov-netzke-basepack
17
+
18
+ Include it into environment.rb:
19
+ config.gem "skozlov-netzke-basepack"
20
+
21
+ Include mapping for Netzke controller providing *.js and *.css (in routes.rb):
22
+ map.netzke
23
+
24
+ = Usage
25
+ First, run the generators to have the necessary migrations:
26
+ ./script/generate netzke_core
27
+
28
+ Do the migrations:
29
+ rake db:migrate
30
+
31
+ The following example will provide you with a grid-based scaffold for ActiveRecord-model called Book. You may generate it like this:
32
+ ./script/generate model Book title:string amount:integer
33
+ (don't forget to re-run the migrations after it)
34
+
35
+ In the controller declare the widget:
36
+
37
+ class WelcomeController < ApplicationController
38
+ netzke :books, :widget_class_name => 'GridPanel', :data_class_name => 'Book'
39
+ end
40
+
41
+ After a widget is declared in the controller, it can be accessed in 3 different ways: 1) loaded by means of an automatically created controller action which will produce a basic HTML-page with the widget (handy for testing), 2) embedded directly into a view (by means of helpers), 3) dynamically loaded by other widgets (like BasicApp-derived, if you want a desktop-like, AJAX-driven web-app).
42
+
43
+ == Using automatically created controller action
44
+ Without writing any more code, you can access the widget by http://localhost:3000/welcome/books_test. That is to say, you simply append _test to your widget's name (as declared in the controller) to get the action's name.
45
+
46
+ == Embedding a widget into a view
47
+ netzke-core plugin provides the following 2 helpers to put inside your head-tag (use it in your layout):
48
+
49
+ 1. netzke_js_include - to include extjs and netzke javascript files
50
+ 2. netzke_css_include - to include the css. This one can take a parameter to specify a color schema you wish for Ext JS, e.g.: netzke_css_include(:gray)
51
+
52
+ Declaring a widget in the controller provides you with a couple of helpers that can be used in the view:
53
+
54
+ 1. books_class_definition will contain the JavaScript code defining the code for the JS class.
55
+ 2. books_widget_instance will declare a local JavaScript variable called 'book'.
56
+ 3. books_widget_render will provide the JavaScript code that calls the "render" method on the variable declared by books_widget_instance.
57
+
58
+ Use these helpers inside of the script-tag like the following (in the view):
59
+
60
+ <script type="text/javascript" charset="utf-8">
61
+ <%= books_class_definition %>
62
+ Ext.onReady(function(){
63
+ <%= books_widget_instance %>
64
+ <%= books_widget_render %>
65
+ })
66
+ </script>
67
+ <div id="books">the widget will be rendered in this div</div>
68
+
69
+ If your layout already calls yield :javascripts wrapped in the <script>-tag, you can have all javascript-code in one place on the page:
70
+
71
+ <% content_for :javascripts do %>
72
+ <%= books_class_definition %>
73
+ Ext.onReady(function(){
74
+ <%= books_widget_instance %>
75
+ books.render("books");
76
+ })
77
+ <% end %>
78
+ <p>... your page content here ...</p>
79
+ <div id="books">the widget will be rendered in this div</div>
80
+
81
+ == Dynamic loading of widgets
82
+ TODO: this part will be covered later
83
+
84
+ = Credentials
85
+ Testing done with the help of http://github.com/pluginaweek/plugin_test_helper
86
+
87
+ Copyright (c) 2008-2009 Sergei Kozlov, released under the MIT license
data/Rakefile CHANGED
@@ -1,14 +1,30 @@
1
- require 'echoe'
1
+ # require 'echoe'
2
+ #
3
+ # Echoe.new("netzke-basepack") do |p|
4
+ # p.author = "Sergei Kozlov"
5
+ # p.email = "sergei@playcode.nl"
6
+ # p.summary = "Prebuilt Netzke widgets for your RIA"
7
+ # p.url = "http://playcode.nl"
8
+ # p.runtime_dependencies = ["binarylogic-searchlogic >= 2.0.0", "skozlov-netzke-core >= 0.4.0"]
9
+ # p.development_dependencies = []
10
+ # p.test_pattern = 'test/**/*_test.rb'
11
+ #
12
+ # # fixing the problem with lib/*-* files being removed while doing manifest
13
+ # p.clean_pattern = ["pkg", "doc", 'build/*', '**/coverage', '**/*.o', '**/*.so', '**/*.a', '**/*.log', "{ext,lib}/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/Makefile", "{ext,lib}/**/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/**/Makefile", "pkg", "*.gem", ".config"]
14
+ # end
2
15
 
3
- Echoe.new("netzke-basepack") do |p|
4
- p.author = "Sergei Kozlov"
5
- p.email = "sergei@writelesscode.com"
6
- p.summary = "Base Netzke widgets - grid, form, tree, and more"
7
- p.url = "http://writelesscode.com"
8
- p.runtime_dependencies = ["searchlogic >=1.6.2", "skozlov-netzke-core >= 0.1.0.2"]
9
- p.development_dependencies = []
10
- p.test_pattern = 'test/**/*_test.rb'
11
-
12
- # fixing the problem with lib/*-* files being removed while doing manifest
13
- p.clean_pattern = ["pkg", "doc", 'build/*', '**/coverage', '**/*.o', '**/*.so', '**/*.a', '**/*.log', "{ext,lib}/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/Makefile", "{ext,lib}/**/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/**/Makefile", "pkg", "*.gem", ".config"]
16
+ begin
17
+ require 'jeweler'
18
+ Jeweler::Tasks.new do |gemspec|
19
+ gemspec.name = "netzke-basepack"
20
+ gemspec.summary = "Pre-built Netzke widgets for your RIA"
21
+ gemspec.description = "Pre-built Netzke widgets for your RIA"
22
+ gemspec.email = "sergei@playcode.nl"
23
+ gemspec.homepage = "http://github.com/skozlov/netzke-basepack"
24
+ gemspec.rubyforge_project = "netzke-basepack"
25
+ gemspec.authors = ["Sergei Kozlov"]
26
+ gemspec.add_dependency "skozlov-netzke-core", ">= 0.4.0"
27
+ end
28
+ rescue LoadError
29
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
14
30
  end
data/TODO.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ == Priority
2
+ * Make column/fields configuration fool-proof
3
+ * Introduce three-state checkbox for SearchPanel
4
+
5
+ == One day
6
+ * Add status bar to BasicApp
7
+ * Replace xcheckbox with checkbox in FormPanel
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1,3 @@
1
+ Autotest.add_discovery do
2
+ "rails"
3
+ end
data/init.rb CHANGED
@@ -1,2 +1 @@
1
- # Include hook code here
2
1
  require 'netzke-basepack'
@@ -1,52 +1,842 @@
1
- // Editors for grid cells and form fields
2
- Ext.netzke.editors = {
3
- combo_box: function(c, config){
4
- var row = Ext.data.Record.create([{name:'id'}])
5
- var store = new Ext.data.Store({
6
- proxy: new Ext.data.HttpProxy({url:config.interface.getCbChoices, jsonData:{column:c.name}}),
7
- reader: new Ext.data.ArrayReader({root:'data', id:0}, row)
8
- })
9
- return new Ext.form.ComboBox({
10
- mode: 'remote',
11
- displayField:'id',
12
- valueField:'id',
13
- triggerAction:'all',
14
- store: store
15
- })
16
- },
17
-
18
- text_field: function(c, config){
19
- return new Ext.form.TextField({
20
- selectOnFocus:true
21
- })
22
- },
23
-
24
- checkbox: function(c, config){
25
- return new Ext.form.TextField({
26
- selectOnFocus:true
27
- })
28
- },
29
-
30
- number_field: function(c, config){
31
- return new Ext.form.NumberField({
32
- selectOnFocus:true
33
- })
34
- },
35
-
36
- // TODO: it's simply a text field for now
37
- datetime: function(c, config){
38
- return new Ext.form.TextField({
39
- selectOnFocus:true
40
- })
41
- }
1
+ Ext.apply(Ext.History, new Ext.util.Observable());
2
+
3
+ // A convenient passfield
4
+ Ext.netzke.PassField = Ext.extend(Ext.form.TextField, {
5
+ inputType: 'password'
6
+ });
7
+ Ext.reg('passfield', Ext.netzke.PassField);
8
+
9
+ // Combobox that knows to talk to the server side (used in both grids and panels)
10
+ Ext.netzke.ComboBox = Ext.extend(Ext.form.ComboBox, {
11
+ displayField : 'id',
12
+ valueField : 'id',
13
+ triggerAction : 'all',
14
+ typeAhead : true,
15
+
16
+ initComponent : function(){
17
+ if (this.options) {
18
+ Ext.apply(this, {
19
+ store : this.options,
20
+ mode : "local"
21
+ });
22
+ } else {
23
+ var row = Ext.data.Record.create([{name:'id'}]);
24
+ var store = new Ext.data.Store({
25
+ proxy : new Ext.data.HttpProxy({url:this.parentId+"__get_combobox_options", jsonData:{column:this.name}}),
26
+ reader : new Ext.data.ArrayReader({root:'data', id:0}, row)
27
+ });
28
+
29
+ Ext.apply(this, {
30
+ store : store
31
+ });
32
+ }
33
+
34
+ Ext.netzke.ComboBox.superclass.initComponent.apply(this, arguments);
35
+
36
+ this.on('blur', function(cb){
37
+ cb.setValue(cb.getRawValue());
38
+ });
39
+
40
+ this.on('specialkey', function(cb, event){
41
+ if (event.getKey() == 9 || event.getKey() == 13) {cb.setValue(cb.getRawValue());}
42
+ });
43
+ }
44
+ });
45
+
46
+ Ext.reg('combobox', Ext.netzke.ComboBox);
47
+
48
+ /*
49
+ Accepts a string which either contains:
50
+ Ext.util.Format method (e.g. 'usMoney'),
51
+ or
52
+ JSON-encoded array, where the first element is Ext.util.Format method (e.g. 'ellipsis'),
53
+ and the rest of the elements - configuration parameters that should be passed to this method besids
54
+ the value to be rendered (e.g. '2');
55
+
56
+ Example of the latter: ["defaultValue", "MyDefaultValue"]
57
+ */
58
+ Ext.netzke.normalizedRenderer = function(config) {
59
+ res = null;
60
+
61
+ if (config) {
62
+ try{
63
+ config = Ext.decode(config); // it's an array consisting of renderer's name *and* eventual options
64
+ } catch(e) {
65
+ // leave config as is - it's supposed to be the renderer's name
66
+ }
67
+
68
+ if (Ext.isArray(config)) {
69
+ res = function(v){
70
+ var formatMethod = config[0];
71
+ var valueAndOptions = config.slice(1);
72
+ valueAndOptions.unshift(v);
73
+ // call the Format function with the argument *and* configuration
74
+ return Ext.util.Format[formatMethod] ? Ext.util.Format[formatMethod].apply(this, valueAndOptions) : "Unknown renderer";
75
+ }
76
+ } else {
77
+ res = Ext.util.Format[config] ? Ext.util.Format[config] : function(v){return "Unknown renderer"}
78
+ }
79
+ }
80
+
81
+ return res;
82
+ };
83
+
84
+ Ext.util.Format.mask = function(v){
85
+ return "********";
42
86
  };
43
87
 
44
- // Mapping of showsAs field to grid filters
88
+ // Mapping of editor field to grid filters
45
89
  Ext.netzke.filterMap = {
46
- number_field:'Numeric',
47
- text_field:'String',
48
- datetime:'String',
49
- checkbox:'Boolean',
50
- combo_box:'String',
51
- date:'Date'
52
- }
90
+ numberfield:'Numeric',
91
+ textfield:'String',
92
+ xdatetime:'String',
93
+ checkbox:'Boolean',
94
+ combobox:'String',
95
+ datefield:'Date'
96
+ };
97
+
98
+ Ext.override(Ext.StatusBar, {
99
+ hideBusy : function(){
100
+ return this.setStatus({
101
+ text: this.defaultText,
102
+ iconCls: this.defaultIconCls
103
+ });
104
+ }
105
+ });
106
+
107
+ Ext.data.RecordArrayReader = Ext.extend(Ext.data.JsonReader, {
108
+ /**
109
+ * Create a data block containing Ext.data.Records from an Array.
110
+ * @param {Object} o An Array of row objects which represents the dataset.
111
+ * @return {Object} data A data block which is used by an Ext.data.Store object as
112
+ * a cache of Ext.data.Records.
113
+ */
114
+ readRecord : function(o){
115
+ var sid = this.meta ? this.meta.id : null;
116
+ var recordType = this.recordType, fields = recordType.prototype.fields;
117
+ var records = [];
118
+ var root = o;
119
+ var n = root;
120
+ var values = {};
121
+ var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
122
+ for(var j = 0, jlen = fields.length; j < jlen; j++){
123
+ var f = fields.items[j];
124
+ var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
125
+ var v = n[k] !== undefined ? n[k] : f.defaultValue;
126
+ v = f.convert(v, n);
127
+ values[f.name] = v;
128
+ }
129
+ var record = new recordType(values, id);
130
+ record.json = n;
131
+ return record;
132
+ }
133
+ });
134
+
135
+ Ext.netzke.JsonField = Ext.extend(Ext.form.TextField, {
136
+ validator: function(value) {
137
+ try{
138
+ var d = Ext.decode(value);
139
+ return true;
140
+ } catch(e) {
141
+ return "Invalid JSON"
142
+ }
143
+ }
144
+
145
+ ,setValue: function(value) {
146
+ this.setRawValue(Ext.encode(value));
147
+ }
148
+
149
+ });
150
+
151
+ Ext.reg('jsonfield', Ext.netzke.JsonField);
152
+
153
+ Ext.ns('Ext.netzke.form');
154
+
155
+ Ext.netzke.form.FileWithType = Ext.extend(Ext.form.Field, {
156
+ defaultAutoCreate:{tag:'input', type:'hidden'},
157
+
158
+ initComponent: function(){
159
+ Ext.netzke.form.FileWithType.superclass.initComponent.call(this);
160
+
161
+ // this.ft = new Ext.form.ComboBox({
162
+ // });
163
+ this.ft = new Ext.form.TextField({
164
+ name: this.name + "_filetype"
165
+ });
166
+ this.ft.ownerCt = this;
167
+
168
+ this.f = new Ext.form.TextField({
169
+ inputType:'file',
170
+ name: this.name + "_file"
171
+ });
172
+ this.f.ownerCt = this;
173
+ },
174
+
175
+ onRender: function(ct, position){
176
+ if(this.isRendered) {
177
+ return;
178
+ }
179
+
180
+ // render underlying hidden field
181
+ Ext.netzke.form.FileWithType.superclass.onRender.call(this, ct, position);
182
+
183
+ var t; // table
184
+
185
+ t = Ext.DomHelper.append(ct, {tag:'table',style:'border-collapse:collapse',children:[
186
+ {tag:'tr',children:[
187
+ {tag:'td', style:'padding:4px', cls:'ux-filewithtype-type'},
188
+ {tag:'td', style:'padding:4px', cls:'ux-filewithtype-file'}
189
+ ]}
190
+ ]}, true);
191
+
192
+ this.ft.render(t.child('td.ux-filewithtype-type'));
193
+ this.f.render(t.child('td.ux-filewithtype-file'));
194
+
195
+ this.el.dom.removeAttribute("name");
196
+
197
+ //
198
+ // this.tableEl = t;
199
+
200
+ // we're rendered flag
201
+ this.isRendered = true;
202
+
203
+ // this.updateHidden();
204
+ }
205
+ });
206
+
207
+ Ext.reg('filewithtype', Ext.netzke.form.FileWithType);
208
+
209
+ /**
210
+ * @class Ext.ux.form.DateTime
211
+ * @extends Ext.form.Field
212
+ *
213
+ * DateTime field, combination of DateField and TimeField
214
+ *
215
+ * @author Ing. Jozef Sak�lo�
216
+ * @copyright (c) 2008, Ing. Jozef Sak�lo�
217
+ * @version 2.0
218
+ * @revision $Id: Ext.ux.form.DateTime.js 513 2009-01-29 19:59:22Z jozo $
219
+ *
220
+ * @license Ext.ux.form.DateTime is licensed under the terms of
221
+ * the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
222
+ * that the code/component(s) do NOT become part of another Open Source or Commercially
223
+ * licensed development library or toolkit without explicit permission.
224
+ *
225
+ * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
226
+ * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
227
+ *
228
+ * @forum 22661
229
+ */
230
+
231
+ Ext.ns('Ext.ux.form');
232
+
233
+ /**
234
+ * @constructor
235
+ * Creates new DateTime
236
+ * @param {Object} config The config object
237
+ */
238
+ Ext.ux.form.DateTime = Ext.extend(Ext.form.Field, {
239
+ /**
240
+ * @cfg {String/Object} defaultAutoCreate DomHelper element spec
241
+ * Let superclass to create hidden field instead of textbox. Hidden will be submittend to server
242
+ */
243
+ defaultAutoCreate:{tag:'input', type:'hidden'}
244
+ /**
245
+ * @cfg {Number} timeWidth Width of time field in pixels (defaults to 100)
246
+ */
247
+ ,timeWidth:80
248
+ /**
249
+ * @cfg {String} dtSeparator Date - Time separator. Used to split date and time (defaults to ' ' (space))
250
+ */
251
+ ,dtSeparator:' '
252
+ /**
253
+ * @cfg {String} hiddenFormat Format of datetime used to store value in hidden field
254
+ * and submitted to server (defaults to 'Y-m-d H:i:s' that is mysql format)
255
+ */
256
+ ,hiddenFormat:'Y-m-d H:i:s'
257
+ /**
258
+ * @cfg {Boolean} otherToNow Set other field to now() if not explicly filled in (defaults to true)
259
+ */
260
+ ,otherToNow:true
261
+ /**
262
+ * @cfg {Boolean} emptyToNow Set field value to now on attempt to set empty value.
263
+ * If it is true then setValue() sets value of field to current date and time (defaults to false)
264
+ */
265
+ /**
266
+ * @cfg {String} timePosition Where the time field should be rendered. 'right' is suitable for forms
267
+ * and 'below' is suitable if the field is used as the grid editor (defaults to 'right')
268
+ */
269
+ ,timePosition:'right' // valid values:'below', 'right'
270
+ /**
271
+ * @cfg {String} dateFormat Format of DateField. Can be localized. (defaults to 'm/y/d')
272
+ */
273
+ ,dateFormat:'m/d/y'
274
+ /**
275
+ * @cfg {String} timeFormat Format of TimeField. Can be localized. (defaults to 'g:i A')
276
+ */
277
+ ,timeFormat:'g:i A'
278
+ /**
279
+ * @cfg {Object} dateConfig Config for DateField constructor.
280
+ */
281
+ /**
282
+ * @cfg {Object} timeConfig Config for TimeField constructor.
283
+ */
284
+
285
+ // {{{
286
+ /**
287
+ * @private
288
+ * creates DateField and TimeField and installs the necessary event handlers
289
+ */
290
+ ,initComponent:function() {
291
+ // call parent initComponent
292
+ Ext.ux.form.DateTime.superclass.initComponent.call(this);
293
+
294
+ // create DateField
295
+ var dateConfig = Ext.apply({}, {
296
+ id:this.id + '-date'
297
+ ,format:this.dateFormat || Ext.form.DateField.prototype.format
298
+ ,width:this.timeWidth
299
+ ,selectOnFocus:this.selectOnFocus
300
+ ,listeners:{
301
+ blur:{scope:this, fn:this.onBlur}
302
+ ,focus:{scope:this, fn:this.onFocus}
303
+ }
304
+ }, this.dateConfig);
305
+ this.df = new Ext.form.DateField(dateConfig);
306
+ this.df.ownerCt = this;
307
+ delete(this.dateFormat);
308
+
309
+
310
+ // create TimeField
311
+ var timeConfig = Ext.apply({}, {
312
+ id:this.id + '-time'
313
+ ,format:this.timeFormat || Ext.form.TimeField.prototype.format
314
+ ,width:this.timeWidth
315
+ ,selectOnFocus:this.selectOnFocus
316
+ ,listeners:{
317
+ blur:{scope:this, fn:this.onBlur}
318
+ ,focus:{scope:this, fn:this.onFocus}
319
+ }
320
+ }, this.timeConfig);
321
+ this.tf = new Ext.form.TimeField(timeConfig);
322
+ this.tf.ownerCt = this;
323
+ delete(this.timeFormat);
324
+
325
+ // relay events
326
+ this.relayEvents(this.df, ['focus', 'specialkey', 'invalid', 'valid']);
327
+ this.relayEvents(this.tf, ['focus', 'specialkey', 'invalid', 'valid']);
328
+
329
+ } // eo function initComponent
330
+ // }}}
331
+ // {{{
332
+ /**
333
+ * @private
334
+ * Renders underlying DateField and TimeField and provides a workaround for side error icon bug
335
+ */
336
+ ,onRender:function(ct, position) {
337
+ // don't run more than once
338
+ if(this.isRendered) {
339
+ return;
340
+ }
341
+
342
+ // render underlying hidden field
343
+ Ext.ux.form.DateTime.superclass.onRender.call(this, ct, position);
344
+
345
+ // render DateField and TimeField
346
+ // create bounding table
347
+ var t;
348
+ if('below' === this.timePosition || 'bellow' === this.timePosition) {
349
+ t = Ext.DomHelper.append(ct, {tag:'table',style:'border-collapse:collapse',children:[
350
+ {tag:'tr',children:[{tag:'td', style:'padding-bottom:1px', cls:'ux-datetime-date'}]}
351
+ ,{tag:'tr',children:[{tag:'td', cls:'ux-datetime-time'}]}
352
+ ]}, true);
353
+ }
354
+ else {
355
+ t = Ext.DomHelper.append(ct, {tag:'table',style:'border-collapse:collapse',children:[
356
+ {tag:'tr',children:[
357
+ {tag:'td',style:'padding-right:4px', cls:'ux-datetime-date'},{tag:'td', cls:'ux-datetime-time'}
358
+ ]}
359
+ ]}, true);
360
+ }
361
+
362
+ this.tableEl = t;
363
+ // this.wrap = t.wrap({cls:'x-form-field-wrap'});
364
+ this.wrap = t.wrap();
365
+ this.wrap.on("mousedown", this.onMouseDown, this, {delay:10});
366
+
367
+ // render DateField & TimeField
368
+ this.df.render(t.child('td.ux-datetime-date'));
369
+ this.tf.render(t.child('td.ux-datetime-time'));
370
+
371
+ // workaround for IE trigger misalignment bug
372
+ if(Ext.isIE && Ext.isStrict) {
373
+ t.select('input').applyStyles({top:0});
374
+ }
375
+
376
+ this.on('specialkey', this.onSpecialKey, this);
377
+ this.df.el.swallowEvent(['keydown', 'keypress']);
378
+ this.tf.el.swallowEvent(['keydown', 'keypress']);
379
+
380
+ // create icon for side invalid errorIcon
381
+ if('side' === this.msgTarget) {
382
+ var elp = this.el.findParent('.x-form-element', 10, true);
383
+ this.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
384
+
385
+ this.df.errorIcon = this.errorIcon;
386
+ this.tf.errorIcon = this.errorIcon;
387
+ }
388
+
389
+ // setup name for submit
390
+ this.el.dom.name = this.hiddenName || this.name || this.id;
391
+
392
+ // prevent helper fields from being submitted
393
+ this.df.el.dom.removeAttribute("name");
394
+ this.tf.el.dom.removeAttribute("name");
395
+
396
+ // we're rendered flag
397
+ this.isRendered = true;
398
+
399
+ // update hidden field
400
+ this.updateHidden();
401
+
402
+ } // eo function onRender
403
+ // }}}
404
+ // {{{
405
+ /**
406
+ * @private
407
+ */
408
+ ,adjustSize:Ext.BoxComponent.prototype.adjustSize
409
+ // }}}
410
+ // {{{
411
+ /**
412
+ * @private
413
+ */
414
+ ,alignErrorIcon:function() {
415
+ this.errorIcon.alignTo(this.tableEl, 'tl-tr', [2, 0]);
416
+ }
417
+ // }}}
418
+ // {{{
419
+ /**
420
+ * @private initializes internal dateValue
421
+ */
422
+ ,initDateValue:function() {
423
+ this.dateValue = this.otherToNow ? new Date() : new Date(1970, 0, 1, 0, 0, 0);
424
+ }
425
+ // }}}
426
+ // {{{
427
+ /**
428
+ * Calls clearInvalid on the DateField and TimeField
429
+ */
430
+ ,clearInvalid:function(){
431
+ this.df.clearInvalid();
432
+ this.tf.clearInvalid();
433
+ } // eo function clearInvalid
434
+ // }}}
435
+ // {{{
436
+ /**
437
+ * @private
438
+ * called from Component::destroy.
439
+ * Destroys all elements and removes all listeners we've created.
440
+ */
441
+ ,beforeDestroy:function() {
442
+ if(this.isRendered) {
443
+ // this.removeAllListeners();
444
+ this.wrap.removeAllListeners();
445
+ this.wrap.remove();
446
+ this.tableEl.remove();
447
+ this.df.destroy();
448
+ this.tf.destroy();
449
+ }
450
+ } // eo function beforeDestroy
451
+ // }}}
452
+ // {{{
453
+ /**
454
+ * Disable this component.
455
+ * @return {Ext.Component} this
456
+ */
457
+ ,disable:function() {
458
+ if(this.isRendered) {
459
+ this.df.disabled = this.disabled;
460
+ this.df.onDisable();
461
+ this.tf.onDisable();
462
+ }
463
+ this.disabled = true;
464
+ this.df.disabled = true;
465
+ this.tf.disabled = true;
466
+ this.fireEvent("disable", this);
467
+ return this;
468
+ } // eo function disable
469
+ // }}}
470
+ // {{{
471
+ /**
472
+ * Enable this component.
473
+ * @return {Ext.Component} this
474
+ */
475
+ ,enable:function() {
476
+ if(this.rendered){
477
+ this.df.onEnable();
478
+ this.tf.onEnable();
479
+ }
480
+ this.disabled = false;
481
+ this.df.disabled = false;
482
+ this.tf.disabled = false;
483
+ this.fireEvent("enable", this);
484
+ return this;
485
+ } // eo function enable
486
+ // }}}
487
+ // {{{
488
+ /**
489
+ * @private Focus date filed
490
+ */
491
+ ,focus:function() {
492
+ this.df.focus();
493
+ } // eo function focus
494
+ // }}}
495
+ // {{{
496
+ /**
497
+ * @private
498
+ */
499
+ ,getPositionEl:function() {
500
+ return this.wrap;
501
+ }
502
+ // }}}
503
+ // {{{
504
+ /**
505
+ * @private
506
+ */
507
+ ,getResizeEl:function() {
508
+ return this.wrap;
509
+ }
510
+ // }}}
511
+ // {{{
512
+ /**
513
+ * @return {Date/String} Returns value of this field
514
+ */
515
+ ,getValue:function() {
516
+ // create new instance of date
517
+ return this.dateValue ? new Date(this.dateValue) : '';
518
+ } // eo function getValue
519
+ // }}}
520
+ // {{{
521
+ /**
522
+ * @return {Boolean} true = valid, false = invalid
523
+ * @private Calls isValid methods of underlying DateField and TimeField and returns the result
524
+ */
525
+ ,isValid:function() {
526
+ return this.df.isValid() && this.tf.isValid();
527
+ } // eo function isValid
528
+ // }}}
529
+ // {{{
530
+ /**
531
+ * Returns true if this component is visible
532
+ * @return {boolean}
533
+ */
534
+ ,isVisible : function(){
535
+ return this.df.rendered && this.df.getActionEl().isVisible();
536
+ } // eo function isVisible
537
+ // }}}
538
+ // {{{
539
+ /**
540
+ * @private Handles blur event
541
+ */
542
+ ,onBlur:function(f) {
543
+ // called by both DateField and TimeField blur events
544
+
545
+ // revert focus to previous field if clicked in between
546
+ if(this.wrapClick) {
547
+ f.focus();
548
+ this.wrapClick = false;
549
+ }
550
+
551
+ // update underlying value
552
+ if(f === this.df) {
553
+ this.updateDate();
554
+ }
555
+ else {
556
+ this.updateTime();
557
+ }
558
+ this.updateHidden();
559
+
560
+ // fire events later
561
+ (function() {
562
+ if(!this.df.hasFocus && !this.tf.hasFocus) {
563
+ var v = this.getValue();
564
+ if(String(v) !== String(this.startValue)) {
565
+ this.fireEvent("change", this, v, this.startValue);
566
+ }
567
+ this.hasFocus = false;
568
+ this.fireEvent('blur', this);
569
+ }
570
+ }).defer(100, this);
571
+
572
+ } // eo function onBlur
573
+ // }}}
574
+ // {{{
575
+ /**
576
+ * @private Handles focus event
577
+ */
578
+ ,onFocus:function() {
579
+ if(!this.hasFocus){
580
+ this.hasFocus = true;
581
+ this.startValue = this.getValue();
582
+ this.fireEvent("focus", this);
583
+ }
584
+ }
585
+ // }}}
586
+ // {{{
587
+ /**
588
+ * @private Just to prevent blur event when clicked in the middle of fields
589
+ */
590
+ ,onMouseDown:function(e) {
591
+ if(!this.disabled) {
592
+ this.wrapClick = 'td' === e.target.nodeName.toLowerCase();
593
+ }
594
+ }
595
+ // }}}
596
+ // {{{
597
+ /**
598
+ * @private
599
+ * Handles Tab and Shift-Tab events
600
+ */
601
+ ,onSpecialKey:function(t, e) {
602
+ var key = e.getKey();
603
+ if(key === e.TAB) {
604
+ if(t === this.df && !e.shiftKey) {
605
+ e.stopEvent();
606
+ this.tf.focus();
607
+ }
608
+ if(t === this.tf && e.shiftKey) {
609
+ e.stopEvent();
610
+ this.df.focus();
611
+ }
612
+ }
613
+ // otherwise it misbehaves in editor grid
614
+ if(key === e.ENTER) {
615
+ this.updateValue();
616
+ }
617
+
618
+ } // eo function onSpecialKey
619
+ // }}}
620
+ // {{{
621
+ /**
622
+ * @private Sets the value of DateField
623
+ */
624
+ ,setDate:function(date) {
625
+ this.df.setValue(date);
626
+ } // eo function setDate
627
+ // }}}
628
+ // {{{
629
+ /**
630
+ * @private Sets the value of TimeField
631
+ */
632
+ ,setTime:function(date) {
633
+ this.tf.setValue(date);
634
+ } // eo function setTime
635
+ // }}}
636
+ // {{{
637
+ /**
638
+ * @private
639
+ * Sets correct sizes of underlying DateField and TimeField
640
+ * With workarounds for IE bugs
641
+ */
642
+ ,setSize:function(w, h) {
643
+ if(!w) {
644
+ return;
645
+ }
646
+ if('below' === this.timePosition) {
647
+ this.df.setSize(w, h);
648
+ this.tf.setSize(w, h);
649
+ if(Ext.isIE) {
650
+ this.df.el.up('td').setWidth(w);
651
+ this.tf.el.up('td').setWidth(w);
652
+ }
653
+ }
654
+ else {
655
+ this.df.setSize(w - this.timeWidth - 4, h);
656
+ this.tf.setSize(this.timeWidth, h);
657
+
658
+ if(Ext.isIE) {
659
+ this.df.el.up('td').setWidth(w - this.timeWidth - 4);
660
+ this.tf.el.up('td').setWidth(this.timeWidth);
661
+ }
662
+ }
663
+ } // eo function setSize
664
+ // }}}
665
+ // {{{
666
+ /**
667
+ * @param {Mixed} val Value to set
668
+ * Sets the value of this field
669
+ */
670
+ ,setValue:function(val) {
671
+ if(!val && true === this.emptyToNow) {
672
+ this.setValue(new Date());
673
+ return;
674
+ }
675
+ else if(!val) {
676
+ this.setDate('');
677
+ this.setTime('');
678
+ this.updateValue();
679
+ return;
680
+ }
681
+ if ('number' === typeof val) {
682
+ val = new Date(val);
683
+ }
684
+ else if('string' === typeof val && this.hiddenFormat) {
685
+ val = Date.parseDate(val, this.hiddenFormat)
686
+ }
687
+ val = val ? val : new Date(1970, 0 ,1, 0, 0, 0);
688
+ var da, time;
689
+ if(val instanceof Date) {
690
+ this.setDate(val);
691
+ this.setTime(val);
692
+ this.dateValue = new Date(val);
693
+ }
694
+ else {
695
+ da = val.split(this.dtSeparator);
696
+ this.setDate(da[0]);
697
+ if(da[1]) {
698
+ if(da[2]) {
699
+ // add am/pm part back to time
700
+ da[1] += da[2];
701
+ }
702
+ this.setTime(da[1]);
703
+ }
704
+ }
705
+ this.updateValue();
706
+ } // eo function setValue
707
+ // }}}
708
+ // {{{
709
+ /**
710
+ * Hide or show this component by boolean
711
+ * @return {Ext.Component} this
712
+ */
713
+ ,setVisible: function(visible){
714
+ if(visible) {
715
+ this.df.show();
716
+ this.tf.show();
717
+ }else{
718
+ this.df.hide();
719
+ this.tf.hide();
720
+ }
721
+ return this;
722
+ } // eo function setVisible
723
+ // }}}
724
+ //{{{
725
+ ,show:function() {
726
+ return this.setVisible(true);
727
+ } // eo function show
728
+ //}}}
729
+ //{{{
730
+ ,hide:function() {
731
+ return this.setVisible(false);
732
+ } // eo function hide
733
+ //}}}
734
+ // {{{
735
+ /**
736
+ * @private Updates the date part
737
+ */
738
+ ,updateDate:function() {
739
+
740
+ var d = this.df.getValue();
741
+ if(d) {
742
+ if(!(this.dateValue instanceof Date)) {
743
+ this.initDateValue();
744
+ if(!this.tf.getValue()) {
745
+ this.setTime(this.dateValue);
746
+ }
747
+ }
748
+ this.dateValue.setMonth(0); // because of leap years
749
+ this.dateValue.setFullYear(d.getFullYear());
750
+ this.dateValue.setMonth(d.getMonth(), d.getDate());
751
+ // this.dateValue.setDate(d.getDate());
752
+ }
753
+ else {
754
+ this.dateValue = '';
755
+ this.setTime('');
756
+ }
757
+ } // eo function updateDate
758
+ // }}}
759
+ // {{{
760
+ /**
761
+ * @private
762
+ * Updates the time part
763
+ */
764
+ ,updateTime:function() {
765
+ var t = this.tf.getValue();
766
+ if(t && !(t instanceof Date)) {
767
+ t = Date.parseDate(t, this.tf.format);
768
+ }
769
+ if(t && !this.df.getValue()) {
770
+ this.initDateValue();
771
+ this.setDate(this.dateValue);
772
+ }
773
+ if(this.dateValue instanceof Date) {
774
+ if(t) {
775
+ this.dateValue.setHours(t.getHours());
776
+ this.dateValue.setMinutes(t.getMinutes());
777
+ this.dateValue.setSeconds(t.getSeconds());
778
+ }
779
+ else {
780
+ this.dateValue.setHours(0);
781
+ this.dateValue.setMinutes(0);
782
+ this.dateValue.setSeconds(0);
783
+ }
784
+ }
785
+ } // eo function updateTime
786
+ // }}}
787
+ // {{{
788
+ /**
789
+ * @private Updates the underlying hidden field value
790
+ */
791
+ ,updateHidden:function() {
792
+ if(this.isRendered) {
793
+ var value = this.dateValue instanceof Date ? this.dateValue.format(this.hiddenFormat) : '';
794
+ this.el.dom.value = value;
795
+ }
796
+ }
797
+ // }}}
798
+ // {{{
799
+ /**
800
+ * @private Updates all of Date, Time and Hidden
801
+ */
802
+ ,updateValue:function() {
803
+
804
+ this.updateDate();
805
+ this.updateTime();
806
+ this.updateHidden();
807
+
808
+ return;
809
+ } // eo function updateValue
810
+ // }}}
811
+ // {{{
812
+ /**
813
+ * @return {Boolean} true = valid, false = invalid
814
+ * calls validate methods of DateField and TimeField
815
+ */
816
+ ,validate:function() {
817
+ return this.df.validate() && this.tf.validate();
818
+ } // eo function validate
819
+ // }}}
820
+ // {{{
821
+ /**
822
+ * Returns renderer suitable to render this field
823
+ * @param {Object} Column model config
824
+ */
825
+ ,renderer: function(field) {
826
+ var format = field.editor.dateFormat || Ext.ux.form.DateTime.prototype.dateFormat;
827
+ format += ' ' + (field.editor.timeFormat || Ext.ux.form.DateTime.prototype.timeFormat);
828
+ var renderer = function(val) {
829
+ var retval = Ext.util.Format.date(val, format);
830
+ return retval;
831
+ };
832
+ return renderer;
833
+ } // eo function renderer
834
+ // }}}
835
+
836
+ }); // eo extend
837
+
838
+ // register xtype
839
+ Ext.reg('xdatetime', Ext.ux.form.DateTime);
840
+
841
+ // eof
842
+