skozlov-netzke-basepack 0.1.1.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
+