netzke-basepack 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG.rdoc +17 -0
  2. data/README.rdoc +1 -1
  3. data/Rakefile +3 -0
  4. data/TODO.rdoc +1 -4
  5. data/features/form_panel.feature +2 -1
  6. data/features/grid_panel.feature +134 -5
  7. data/features/nested_attributes.feature +4 -1
  8. data/features/paging_form_panel.feature +41 -0
  9. data/features/search_in_grid.feature +20 -7
  10. data/features/step_definitions/form_panel_steps.rb +35 -0
  11. data/features/step_definitions/generic_steps.rb +20 -1
  12. data/features/step_definitions/grid_panel_steps.rb +63 -0
  13. data/features/support/env.rb +4 -0
  14. data/features/validations_in_grid.feature +13 -0
  15. data/features/virtual_attributes.feature +5 -9
  16. data/javascripts/basepack.js +21 -650
  17. data/javascripts/datetimefield.js +24 -0
  18. data/lib/generators/netzke/basepack_generator.rb +10 -0
  19. data/{generators/netzke_basepack/templates/public_assets → lib/generators/netzke/templates/assets}/ts-checkbox.gif +0 -0
  20. data/{generators/netzke_basepack → lib/generators/netzke}/templates/create_netzke_field_lists.rb +0 -0
  21. data/lib/netzke-basepack.rb +3 -41
  22. data/lib/netzke/active_record/attributes.rb +17 -21
  23. data/lib/netzke/basepack.rb +6 -1
  24. data/lib/netzke/basepack/data_accessor.rb +166 -0
  25. data/lib/netzke/basepack/form_panel.rb +69 -20
  26. data/lib/netzke/basepack/form_panel/fields.rb +15 -17
  27. data/lib/netzke/basepack/form_panel/javascripts/comma_list_cbg.js +7 -0
  28. data/lib/netzke/basepack/form_panel/javascripts/display_mode.js +6 -0
  29. data/lib/netzke/basepack/form_panel/javascripts/{main.js → form_panel.js} +42 -8
  30. data/lib/netzke/basepack/form_panel/javascripts/n_radio_group.js +24 -7
  31. data/lib/netzke/basepack/form_panel/javascripts/readonly_mode.js +52 -0
  32. data/lib/netzke/basepack/form_panel/services.rb +28 -2
  33. data/lib/netzke/basepack/form_panel/stylesheets/readonly_mode.css +14 -0
  34. data/lib/netzke/basepack/grid_panel.rb +151 -181
  35. data/lib/netzke/basepack/grid_panel/columns.rb +122 -84
  36. data/lib/netzke/basepack/grid_panel/javascripts/advanced_search.js +22 -27
  37. data/lib/netzke/basepack/grid_panel/javascripts/{main.js → grid_panel.js} +182 -108
  38. data/lib/netzke/basepack/grid_panel/record_form_window.rb +7 -2
  39. data/lib/netzke/basepack/grid_panel/services.rb +58 -39
  40. data/lib/netzke/basepack/paging_form_panel.rb +86 -25
  41. data/lib/netzke/basepack/query_builder.rb +105 -0
  42. data/lib/netzke/basepack/query_builder/javascripts/query_builder.js +140 -0
  43. data/lib/netzke/basepack/search_panel.rb +61 -44
  44. data/lib/netzke/basepack/search_panel/javascripts/condition_field.js +153 -0
  45. data/lib/netzke/basepack/search_panel/javascripts/search_panel.js +64 -0
  46. data/lib/netzke/basepack/search_window.rb +64 -0
  47. data/lib/netzke/basepack/simple_app.rb +1 -1
  48. data/lib/netzke/basepack/simple_app/javascripts/{main.js → simple_app.js} +0 -0
  49. data/lib/netzke/basepack/tab_panel.rb +1 -2
  50. data/lib/netzke/basepack/tab_panel/javascripts/{main.js → tab_panel.js} +0 -0
  51. data/lib/netzke/basepack/version.rb +1 -1
  52. data/locales/en.yml +71 -4
  53. data/netzke-basepack.gemspec +47 -22
  54. data/spec/active_record/attributes_spec.rb +6 -6
  55. data/spec/components/form_panel_spec.rb +2 -13
  56. data/stylesheets/datetimefield.css +54 -0
  57. data/test/rails_app/Gemfile +3 -3
  58. data/test/rails_app/Gemfile.lock +67 -57
  59. data/test/rails_app/README +1 -256
  60. data/test/rails_app/app/components/book_form.rb +1 -3
  61. data/test/rails_app/app/components/book_form_with_custom_fields.rb +20 -0
  62. data/test/rails_app/app/components/book_form_with_nested_attributes.rb +18 -0
  63. data/test/rails_app/app/components/book_grid.rb +3 -1
  64. data/test/rails_app/app/components/book_grid_loader.rb +24 -0
  65. data/test/rails_app/app/components/book_grid_with_custom_columns.rb +28 -0
  66. data/test/rails_app/app/components/book_grid_with_default_values.rb +1 -1
  67. data/test/rails_app/app/components/book_grid_with_virtual_attributes.rb +0 -1
  68. data/test/rails_app/app/components/book_paging_form_panel.rb +3 -2
  69. data/test/rails_app/app/components/book_presentation.rb +3 -3
  70. data/test/rails_app/app/components/book_query_builder.rb +8 -0
  71. data/test/rails_app/app/components/book_search_panel.rb +5 -0
  72. data/test/rails_app/app/components/book_search_panel/javascripts/i18n_de.js +6 -0
  73. data/test/rails_app/app/components/double_book_grid.rb +18 -0
  74. data/test/rails_app/app/components/form_without_model.rb +1 -1
  75. data/test/rails_app/app/components/paging_form_with_search.rb +39 -0
  76. data/test/rails_app/app/components/user_grid.rb +1 -1
  77. data/test/rails_app/app/models/author.rb +1 -0
  78. data/test/rails_app/config/application.rb +6 -1
  79. data/test/rails_app/config/database.yml +7 -5
  80. data/test/rails_app/config/environments/test.rb +1 -1
  81. data/test/rails_app/config/locales/de.yml +35 -0
  82. data/test/rails_app/config/locales/es.yml +84 -8
  83. data/test/rails_app/db/migrate/20101026190021_create_books.rb +2 -2
  84. data/test/rails_app/db/migrate/20110213213050_create_netzke_component_states.rb +20 -0
  85. data/test/rails_app/db/schema.rb +2 -18
  86. data/test/rails_app/public/netzke/basepack/ts-checkbox.gif +0 -0
  87. data/test/unit/active_record_basepack_test.rb +1 -1
  88. metadata +46 -45
  89. data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -13
  90. data/lib/netzke/basepack/grid_panel/search_window.rb +0 -56
  91. data/lib/netzke/data_accessor.rb +0 -113
  92. data/lib/netzke/ext.rb +0 -7
  93. data/lib/netzke/fields_configurator.rb +0 -170
  94. data/lib/netzke/json_array_editor.rb +0 -67
  95. data/lib/netzke/masquerade_selector.rb +0 -53
  96. data/test/rails_app/app/components/some_search_panel.rb +0 -34
  97. data/test/rails_app/db/development_structure.sql +0 -93
  98. data/test/rails_app/db/migrate/20100905214933_create_netzke_preferences.rb +0 -16
@@ -0,0 +1,140 @@
1
+ {
2
+ initComponent: function() {
3
+ Netzke.classes.Basepack.QueryBuilder.superclass.initComponent.call(this);
4
+ this.add({title: "+"});
5
+
6
+ this.on('beforetabchange', function(c, newTab, curentTab){
7
+ if (newTab.title === '+') {
8
+ this.addTab(true);
9
+ return false;
10
+ } else {
11
+ if (this.maxTabHeight) newTab.setHeight(this.maxTabHeight);
12
+ }
13
+ }, this);
14
+
15
+ this.addEvents('conditionsupdate');
16
+
17
+ },
18
+
19
+ buildFormFromQuery: function(query) {
20
+ this.onClearAll();
21
+
22
+ if (query.length !== 0) {
23
+ Ext.each(query, function(f, i){
24
+ if (this.items.getCount() < i + 2) { this.addTab(); }
25
+ this.items.get(i).buildFormFromQuery(query[i]);
26
+ }, this);
27
+ }
28
+
29
+ this.doLayout();
30
+ },
31
+
32
+ addTab: function(activate){
33
+ var newTabConfig = Ext.apply({}, this.components.searchPanel);
34
+ newTabConfig.id = Ext.id(); // We need a unique ID every time
35
+ newTabConfig.title = "OR";
36
+ newTabConfig.closable = true;
37
+ var newTab = Ext.create(newTabConfig);
38
+
39
+ this.insert(this.items.getCount() - 1, newTab);
40
+
41
+ if (activate) {
42
+ this.suspendEvents();
43
+ this.activate(newTab);
44
+ this.resumeEvents();
45
+ }
46
+ },
47
+
48
+ getQuery: function(all) {
49
+ var query = [];
50
+ this.eachTab(function(i) {
51
+ var q = i.getQuery();
52
+ if (q.length > 0) query.push(i.getQuery(all));
53
+ });
54
+ return query;
55
+ },
56
+
57
+ getTabs: function() {
58
+ var res = [];
59
+ this.items.each(function(i) {
60
+ if (i.title !== "+") { res.push(i); }
61
+ });
62
+ return res;
63
+ },
64
+
65
+ eachTab: function(fn, scope) {
66
+ this.items.each(function(f, i) {
67
+ if (this.items.last() !== f) {
68
+ fn.call(scope || f, f);
69
+ }
70
+ }, this);
71
+ },
72
+
73
+ onClearAll: function() {
74
+ this.removeAllTabs(true);
75
+ this.items.first().onClearAll();
76
+ },
77
+
78
+ onReset: function() {
79
+ this.eachTab(function(t) { t.onReset(); });
80
+ },
81
+
82
+ removeAllTabs: function(exceptLast) {
83
+ this.eachTab(function(t) { if (this.items.getCount() > (exceptLast ? 2 : 1)) {this.remove(t);} }, this);
84
+ },
85
+
86
+ onSavePreset: function(){
87
+ var searchName = this.presetsCombo.getRawValue();
88
+
89
+ if (searchName !== "") {
90
+ var presetsComboStore = this.presetsCombo.getStore();
91
+ var existingPresetIndex = presetsComboStore.find('field2', searchName);
92
+ if (existingPresetIndex !== -1) {
93
+ // overwriting
94
+ Ext.Msg.confirm(this.i18n.overwriteConfirmTitle, String.format(this.i18n.overwriteConfirm, searchName), function(btn, text){
95
+ if (btn == 'yes') {
96
+ var r = presetsComboStore.getAt(existingPresetIndex);
97
+ r.set('field1', this.getQuery(true));
98
+ r.commit();
99
+ this.doSavePreset(searchName);
100
+ }
101
+ }, this);
102
+ } else {
103
+ this.doSavePreset(searchName);
104
+ var r = new presetsComboStore.recordType({field1: this.getQuery(true), field2: searchName});
105
+ presetsComboStore.add(r);
106
+ }
107
+ }
108
+ },
109
+
110
+ doSavePreset: function(name){
111
+ this.savePreset({
112
+ name: name,
113
+ query: Ext.encode(this.getQuery(true))
114
+ });
115
+ },
116
+
117
+ onDeletePreset: function(){
118
+ var searchName = this.presetsCombo.getRawValue();
119
+ if (searchName !== "") {
120
+ Ext.Msg.confirm(this.i18n.deleteConfirmTitle, String.format(this.i18n.overwriteConfirm, searchName), function(btn, text){
121
+ if (btn == 'yes') {
122
+ this.removePresetFromList(searchName);
123
+ this.deletePreset({
124
+ name: searchName
125
+ });
126
+ }
127
+ }, this);
128
+ }
129
+ },
130
+
131
+ onApply: function() {
132
+ this.fireEvent('conditionsupdate', this.getQuery());
133
+ },
134
+
135
+ removePresetFromList: function(name){
136
+ var presetsComboStore = this.presetsCombo.getStore();
137
+ presetsComboStore.removeAt(presetsComboStore.find('field2', name));
138
+ this.presetsCombo.reset();
139
+ }
140
+ }
@@ -1,59 +1,76 @@
1
1
  module Netzke
2
2
  module Basepack
3
- class SearchPanel < FormPanel
4
-
5
- js_properties :header => false,
6
- :bbar => false
7
-
8
- # An override
9
- def normalize_field(f)
10
- f = if f.is_a?(Symbol) || f.is_a?(String)
11
- {:name => f.to_s, :operator => default_operator}
12
- else
13
- search_condition = f[:name]
14
- if search_condition.is_a?(MetaWhere::Column)
15
- {:name => search_condition.column, :operator => search_condition.method}
16
- else
17
- {:name => search_condition.to_s}
18
- end
19
- end
3
+ # == Configuration
4
+ # +load_last_preset+ - on load, tries to load the latest saved preset
5
+ class SearchPanel < Base
20
6
 
21
- f = super(f)
7
+ js_base_class "Ext.form.FormPanel"
22
8
 
23
- f[:disabled] = primary_key_attr?(f)
9
+ js_properties(
10
+ :padding => 5,
11
+ :auto_scroll => true
12
+ )
24
13
 
25
- # Association field
26
- if f[:name].to_s.index("__")
27
- f[:xtype] ||= xtype_for_attr_type(:string)
28
- f[:attr_type] = :string
29
- end
14
+ js_include :condition_field
30
15
 
31
- f[:operator] ||= "gt" if [:datetime, :integer, :date].include?(f[:attr_type])
32
- f[:operator] ||= "eq" if f[:attr_type] == :boolean
33
- f[:operator] ||= default_operator
16
+ js_mixin :search_panel
34
17
 
35
- f[:field_label] = [f[:field_label], f[:operator]].join(" ")
36
- f.merge(:name => [f[:name], f[:operator]].join("__"))
37
- end
18
+ js_property :attribute_operators_map, {
19
+ :integer => [
20
+ ["eq", I18n.t('netzke.basepack.search_panel.equals')],
21
+ ["gt", I18n.t('netzke.basepack.search_panel.greater_than')],
22
+ ["lt", I18n.t('netzke.basepack.search_panel.less_than')]
23
+ ],
24
+ :text => [
25
+ ["contains", I18n.t('netzke.basepack.search_panel.contains')] # same as matches => %string%
26
+ ],
27
+ :string => [
28
+ ["contains", I18n.t('netzke.basepack.search_panel.contains')], # same as matches => %string%
29
+ ["matches", I18n.t('netzke.basepack.search_panel.matches')]
30
+ ],
31
+ :boolean => [
32
+ ["is_any", I18n.t('netzke.basepack.search_panel.is_true')],
33
+ ["is_true", I18n.t('netzke.basepack.search_panel.is_true')],
34
+ ["is_false", I18n.t('netzke.basepack.search_panel.is_false')]
35
+ ],
36
+ :datetime => [
37
+ ["eq", I18n.t('netzke.basepack.search_panel.date_equals')],
38
+ ["gt", I18n.t('netzke.basepack.search_panel.after')],
39
+ ["lt", I18n.t('netzke.basepack.search_panel.before')]
40
+ ]
41
+ }
38
42
 
39
- private
40
- def default_operator
41
- "like"
43
+ # Builds default query search panel, where each field is presented
44
+ def default_query
45
+ data_class.column_names.map do |c|
46
+ column_type = data_class.columns_hash[c].type
47
+ operator = (self.class.js_property(:attribute_operators_map)[column_type] || []).first.try(:fetch, 0) || "matches"
48
+ {:attr => c, :attr_type => column_type, :operator => operator}
42
49
  end
50
+ end
43
51
 
44
- # we need to correct the queries to cut off the condition suffixes, otherwise the FormPanel gets confused
45
- def get_combobox_options(params)
46
- column_name = params[:column]
47
- CONDITIONS.each { |c| column_name.sub!(/_#{c}$/, "") }
48
- super(:column => column_name)
49
- end
52
+ def data_class
53
+ @data_class ||= config[:model].constantize
54
+ end
50
55
 
51
- def attr_type_to_xtype_map
52
- super.merge({
53
- :boolean => :tricheckbox
54
- })
56
+ def js_config
57
+ super.merge(
58
+ :attrs => attributes,
59
+ :attrs_hash => data_class.column_names.inject({}){ |hsh,c| hsh.merge(c => data_class.columns_hash[c].type) },
60
+ :query => (config[:load_last_preset] ? last_preset.try(:fetch, "query") : config[:query]) || []
61
+ )
62
+ end
63
+
64
+ def attributes
65
+ data_class.column_names.map do |name|
66
+ [name, data_class.human_attribute_name(name)]
55
67
  end
68
+ end
69
+
70
+ def last_preset
71
+ (state[:presets] || []).last
72
+ end
56
73
 
57
74
  end
58
75
  end
59
- end
76
+ end
@@ -0,0 +1,153 @@
1
+ Ext.ns("Netzke.classes.Basepack.SearchPanel");
2
+
3
+ Netzke.classes.Basepack.SearchPanel.ConditionField = Ext.extend(Ext.form.CompositeField, {
4
+ hideLabel: true,
5
+
6
+ // Config refinements for the value field
7
+ valueFieldConfigs: {
8
+ string: {
9
+ xtype : 'textfield'
10
+ },
11
+ datetime: {
12
+ xtype : 'datefield'
13
+ },
14
+ integer: {
15
+ xtype: 'numberfield'
16
+ }
17
+ },
18
+
19
+ initComponent: function(){
20
+ // if (!this.attrType) this.attrType = 'string';
21
+ var items = [
22
+ // attribute combo
23
+ {
24
+ xtype : 'combo',
25
+ store: this.ownerCt.attrs,
26
+ allowBlank: true,
27
+ name: this.attr + '_attr',
28
+ ref: 'attrCombo',
29
+ emptyText: 'Attribute',
30
+ triggerAction: 'all',
31
+ value: this.attr ? this.attr.underscore() : "",
32
+ listeners: {
33
+ select: this.onAttributeChange,
34
+ scope: this
35
+ }
36
+ }
37
+ ];
38
+
39
+ if (this.attr) {
40
+
41
+ var operators = this.ownerCt.attributeOperatorsMap[this.attrType] || [[]];
42
+
43
+ if (this.attrType === 'boolean') {
44
+ items.push({
45
+ xtype : 'tricheckbox',
46
+ width: 100,
47
+ name: this.attr + '_value',
48
+ ref: 'valueField',
49
+ checked: this.value
50
+ });
51
+
52
+ items.push({
53
+ flex: 1,
54
+ xtype: 'displayfield'
55
+ });
56
+ } else {
57
+ // operator combo
58
+ items.push(
59
+ {
60
+ width: 100,
61
+ xtype : 'combo',
62
+ fieldLabel: 'Operator',
63
+ store: operators,
64
+ name: this.attr + '_operator',
65
+ value: this.operator || operators[0][0],
66
+ autoSelect: true,
67
+ triggerAction: 'all',
68
+ emptyText: "Operator",
69
+ ref: "operatorCombo"
70
+ }
71
+ );
72
+
73
+ // value field
74
+ items.push(Ext.apply(
75
+ {
76
+ xtype : 'textfield',
77
+ emptyText: "Value",
78
+ flex: 1,
79
+ name: this.attr + '_value',
80
+ ref: 'valueField',
81
+ value: this.value,
82
+ ref: "valueField"
83
+ },
84
+ this.valueFieldConfigs[this.attrType] // refining the config dependent on the attr type
85
+ ));
86
+ }
87
+
88
+ // delete button
89
+ items.push({
90
+ xtype: 'button',
91
+ cls: 'x-btn-icon',
92
+ icon: Netzke.RelativeUrlRoot + "/images/icons/cross.png",
93
+ handler: this.removeSelf,
94
+ scope: this
95
+ });
96
+ }
97
+
98
+ this.items = items;
99
+
100
+ // Why on Earth is this not working? Netzke.classes.Basepack.SearchPanel.ConditionField undefined???
101
+ // Netzke.classes.Basepack.SearchPanel.ConditionField.superclass.initComponent.call(this);
102
+ Ext.form.CompositeField.prototype.initComponent.call(this); // workaround
103
+
104
+ this.addEvents('configured'); // user selects the attribute from the attribute combo
105
+ },
106
+
107
+ isConfigured: function() {
108
+ return !!this.attrCombo.getValue();
109
+ },
110
+
111
+ removeSelf: function(){
112
+ var ownerCt = this.ownerCt;
113
+ this.destroy();
114
+ ownerCt.fireEvent('fieldsnumberchange');
115
+ },
116
+
117
+ onAttributeChange: function(e){
118
+ this.fireEvent('configured');
119
+ this.changeAttribute(e.value.camelize(true));
120
+ },
121
+
122
+ // Dynamically replace self with a field with different attrType
123
+ changeAttribute: function(attr){
124
+ var attrType = this.ownerCt.attrsHash[attr];
125
+ var idx = this.ownerCt.items.indexOf(this);
126
+ var owner = this.ownerCt;
127
+ var newSelf = Ext.create(Ext.apply(this.initialConfig, {name: attr, attrType: attrType, attr: attr.underscore(), ownerCt: this.ownerCt, operator: null}));
128
+ owner.remove(this);
129
+ owner.insert(idx, newSelf);
130
+ owner.doLayout();
131
+ },
132
+
133
+ // Returns true if it should be in the query
134
+ valueIsSet: function(){
135
+ return !!(this.attrCombo.getValue() && (this.attrType === 'boolean' || this.operatorCombo.getValue()) && !Ext.isEmpty(this.valueField.getValue()));
136
+ },
137
+
138
+ // Returns the query object
139
+ buildValue: function(){
140
+ var res = {attr: this.attrCombo.getValue(), value: this.valueField.getValue()};
141
+ if (this.attrType !== 'boolean') {
142
+ res.operator = this.operatorCombo.getValue();
143
+ }
144
+ return res;
145
+ },
146
+
147
+ clearValue: function() {
148
+ this.valueField.reset();
149
+ }
150
+
151
+ });
152
+
153
+ Ext.reg('netzkebasepacknewsearchpanelconditionfield', Netzke.classes.Basepack.SearchPanel.ConditionField);
@@ -0,0 +1,64 @@
1
+ {
2
+ initComponent: function() {
3
+ Netzke.classes.Basepack.SearchPanel.superclass.initComponent.call(this);
4
+ this.buildFormFromQuery(this.query);
5
+
6
+ this.onAddCondition();
7
+
8
+ },
9
+
10
+ // Will probably need to be performance-optimized in the future, as recreating the fields is expensive
11
+ buildFormFromQuery: function(query) {
12
+ this.onClearAll();
13
+ Ext.each(query, function(f){
14
+ this.insert(this.items.length - 1, Ext.apply(f, {xtype: 'netzkebasepacknewsearchpanelconditionfield'}));
15
+ }, this);
16
+ this.doLayout();
17
+ },
18
+
19
+ onAddCondition: function() {
20
+ var condField = Ext.create({xtype: 'netzkebasepacknewsearchpanelconditionfield', ownerCt: this});
21
+ condField.on('configured', function() {
22
+ this.onAddCondition();
23
+ }, this, {single: true});
24
+ this.add(condField);
25
+ this.doLayout();
26
+ this.fireEvent('fieldsnumberchange');
27
+ },
28
+
29
+ onReset: function() {
30
+ this.items.each(function(f){
31
+ if (f.valueField) {f.clearValue();}
32
+ });
33
+ },
34
+
35
+ onClearAll: function() {
36
+ this.eachConfiguredField(function(f) {
37
+ this.remove(f);
38
+ }, this);
39
+
40
+ this.fireEvent('fieldsnumberchange');
41
+ },
42
+
43
+ // Returns each condition field which has attribute selected
44
+ eachConfiguredField: function(fn, scope) {
45
+ this.items.each(function(f, i) {
46
+ if (this.items.last() !== f) {
47
+ fn.call(scope || f, f);
48
+ }
49
+ }, this);
50
+ },
51
+
52
+ // When "all" is "true", also includes the fields with empty values
53
+ getQuery: function(all) {
54
+ var query = [];
55
+ this.eachConfiguredField(function(f){
56
+ if (f.valueIsSet() || all) {
57
+ var cond = f.buildValue();
58
+ if (all) {cond.attrType = f.attrType;}
59
+ query.push(cond);
60
+ }
61
+ });
62
+ return query;
63
+ }
64
+ }