mir_extensions 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/.bundle/config +2 -0
  2. data/.document +5 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +1 -0
  5. data/Gemfile +41 -0
  6. data/Gemfile.lock +116 -0
  7. data/LICENSE +20 -0
  8. data/README +256 -0
  9. data/README.rdoc +17 -0
  10. data/Rakefile +50 -0
  11. data/VERSION +1 -0
  12. data/app/controllers/application_controller.rb +9 -0
  13. data/app/helpers/application_helper.rb +2 -0
  14. data/app/models/primary.rb +11 -0
  15. data/app/models/secondary.rb +7 -0
  16. data/app/views/layouts/application.html.erb +14 -0
  17. data/autotest/discover.rb +2 -0
  18. data/config.ru +4 -0
  19. data/config/application.rb +47 -0
  20. data/config/boot.rb +13 -0
  21. data/config/database.yml +17 -0
  22. data/config/environment.rb +5 -0
  23. data/config/environments/development.rb +22 -0
  24. data/config/environments/production.rb +49 -0
  25. data/config/environments/test.rb +35 -0
  26. data/config/initializers/backtrace_silencers.rb +7 -0
  27. data/config/initializers/inflections.rb +10 -0
  28. data/config/initializers/mime_types.rb +5 -0
  29. data/config/initializers/secret_token.rb +7 -0
  30. data/config/initializers/session_store.rb +8 -0
  31. data/config/locales/en.yml +5 -0
  32. data/config/routes.rb +58 -0
  33. data/db/development.sqlite3 +1 -0
  34. data/db/mir_ext_development.sqlite3 +0 -0
  35. data/db/mir_ext_test.sqlite3 +0 -0
  36. data/db/schema.rb +26 -0
  37. data/db/seeds.rb +7 -0
  38. data/db/test.sqlite3 +0 -0
  39. data/doc/README_FOR_APP +2 -0
  40. data/lib/core_ext/controller_extensions.rb +37 -0
  41. data/lib/core_ext/core_ext.rb +344 -0
  42. data/lib/core_ext/helper_extensions.rb +383 -0
  43. data/lib/mir_extensions.rb +37 -0
  44. data/lib/tasks/.gitkeep +0 -0
  45. data/log/development.log +151 -0
  46. data/log/production.log +0 -0
  47. data/log/server.log +0 -0
  48. data/log/test.log +27 -0
  49. data/mir_extensions.gemspec +119 -0
  50. data/public/404.html +26 -0
  51. data/public/422.html +26 -0
  52. data/public/500.html +26 -0
  53. data/public/favicon.ico +0 -0
  54. data/public/images/rails.png +0 -0
  55. data/public/index.html +262 -0
  56. data/public/javascripts/application.js +2 -0
  57. data/public/javascripts/controls.js +965 -0
  58. data/public/javascripts/dragdrop.js +974 -0
  59. data/public/javascripts/effects.js +1123 -0
  60. data/public/javascripts/prototype.js +6001 -0
  61. data/public/javascripts/rails.js +175 -0
  62. data/public/robots.txt +5 -0
  63. data/public/stylesheets/.gitkeep +0 -0
  64. data/script/rails +6 -0
  65. data/spec/controllers/application_controller_spec.rb +41 -0
  66. data/spec/helpers/application_helper_spec.rb +40 -0
  67. data/spec/mir_extensions_spec.rb +269 -0
  68. data/spec/spec_helper.rb +27 -0
  69. data/vendor/plugins/.gitkeep +0 -0
  70. metadata +170 -0
@@ -0,0 +1 @@
1
+ S
Binary file
@@ -0,0 +1,26 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # Note that this schema.rb definition is the authoritative source for your
6
+ # database schema. If you need to create the application database on another
7
+ # system, you should be using db:schema:load, not running all the migrations
8
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
10
+ #
11
+ # It's strongly recommended to check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(:version => 0) do
14
+
15
+ create_table "primaries", :force => true do |t|
16
+ t.string "name"
17
+ end
18
+
19
+ create_table "secondaries", :force => true do |t|
20
+ t.string "name"
21
+ t.integer "primary_id"
22
+ end
23
+
24
+ add_index "secondaries", ["primary_id"], :name => "index_roles_on_name"
25
+
26
+ end
@@ -0,0 +1,7 @@
1
+ # This file should contain all the record creation needed to seed the database with its default values.
2
+ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3
+ #
4
+ # Examples:
5
+ #
6
+ # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
7
+ # Mayor.create(:name => 'Daley', :city => cities.first)
Binary file
@@ -0,0 +1,2 @@
1
+ Use this README file to introduce your application and point to useful places in the API for learning more.
2
+ Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
@@ -0,0 +1,37 @@
1
+ class ActionController::Base
2
+ require 'mir_extensions'
3
+ require 'socket'
4
+
5
+ def self.local_ip
6
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
7
+
8
+ UDPSocket.open do |s|
9
+ s.connect '64.233.187.99', 1
10
+ s.addr.last
11
+ end
12
+ ensure
13
+ Socket.do_not_reverse_lookup = orig
14
+ end
15
+
16
+ # Returns a sanitized column parameter suitable for SQL order-by clauses.
17
+ def sanitize_by_param(allowed=[], default='id')
18
+ sanitize_params params && params[:by], allowed, default
19
+ end
20
+
21
+ # Returns a sanitized direction parameter suitable for SQL order-by clauses.
22
+ def sanitize_dir_param
23
+ sanitize_params params && params[:dir], ['ASC', 'DESC'], 'ASC'
24
+ end
25
+
26
+ # Use this method to prevent SQL injection vulnerabilities by verifying that a user-provided
27
+ # parameter is on a whitelist of allowed values.
28
+ #
29
+ # Accepts a value, a list of allowed values, and a default value.
30
+ # Returns the value if allowed, otherwise the default.
31
+ def sanitize_params(supplied='', allowed=[], default=nil)
32
+ raise ArgumentError, "A default value is required." unless default
33
+ return default if supplied.blank? || allowed.blank? || ! allowed.include?(supplied)
34
+ return supplied
35
+ end
36
+ end
37
+
@@ -0,0 +1,344 @@
1
+ require 'rubygems'
2
+ require 'singleton'
3
+ require File.expand_path(File.dirname(__FILE__) + '/controller_extensions')
4
+ require File.expand_path(File.dirname(__FILE__) + '/helper_extensions')
5
+ require 'soap/header/simplehandler'
6
+
7
+ module CoreExt
8
+
9
+ # String Extensions ==============================================================================
10
+ String.class_eval do
11
+
12
+ include ActionView::Helpers::NumberHelper
13
+
14
+ # Usage: "3125552312".to_phone
15
+ # Returns: 312-555-2313
16
+ #
17
+ # Usage: "3125552313".to_phone(:area_code => true)
18
+ # Returns : (312) 555-2313
19
+ #
20
+ def to_phone(options = {})
21
+ number_to_phone(self.to_i, options)
22
+ end
23
+
24
+ end
25
+ end
26
+
27
+ class String
28
+
29
+ # Bring in support for view helpers
30
+ # include MirExtensions::CoreExt::String::NumberHelper
31
+
32
+ # General methods
33
+
34
+ def capitalize_words
35
+ self.downcase.gsub(/\b([a-z])/) { $1.capitalize }.gsub( "'S", "'s" )
36
+ end
37
+
38
+ # Address methods
39
+
40
+ def expand_address_abbreviations
41
+ _address = self.strip.capitalize_words
42
+
43
+ # NOTE: DO NOT rearrange the replace sequences; order matters!
44
+
45
+ # streets
46
+ _address.gsub!( /\b(ave|av)\.?\b/i, 'Avenue ' )
47
+ _address.gsub!( /\b(blvd|blv|bld|bl)\.?\b/i, 'Boulevard ' )
48
+ _address.gsub!( /\bcr\.?\b/i, 'Circle ' )
49
+ _address.gsub!( /\bctr\.?\b/i, 'Center ' )
50
+ _address.gsub!( /\b(crt|ct)\.?\b/i, 'Court ' )
51
+ _address.gsub!( /\bdr\.?\b/i, 'Drive ' )
52
+ _address.gsub!( /\b(expressw|expw|expy)\.?\b/i, 'Expressway ' )
53
+ _address.gsub!( /\bfrwy\.?\b/i, 'Freeway ' )
54
+ _address.gsub!( /\bhwy\.?\b/i, 'Highway ' )
55
+ _address.gsub!( /\bln\.?\b/i, 'Lane ' )
56
+ _address.gsub!( /\b(prkwy|pkwy|pkw|pky)\.?\b/i, 'Parkway ' )
57
+ _address.gsub!( /\bpk\.?\b/i, 'Pike ' )
58
+ _address.gsub!( /\bplz\.?\b/i, 'Plaza ' )
59
+ _address.gsub!( /\bpl\.?\b/i, 'Place ' )
60
+ _address.gsub!( /\brd\.?\b/i, 'Road ' )
61
+ _address.gsub!( /\b(rte|rt)\.?\b/i, 'Route ' )
62
+ _address.gsub!( /\bste\.?\b/i, 'Suite ' )
63
+ _address.gsub!( /\bst\.?\b/i, 'Street ' )
64
+ _address.gsub!( /\btrpk\.?\b/i, 'Turnpike ' )
65
+ _address.gsub!( /\btr\.?\b/i, 'Trail ' )
66
+
67
+ # directions
68
+ _address.gsub!( /\bN\.?e\.?\b/i, 'Northeast ' )
69
+ _address.gsub!( /\bS\.?e\.?\b/i, 'Southeast ' )
70
+ _address.gsub!( /\bS\.?w\.?\b/i, 'Southwest ' )
71
+ _address.gsub!( /\bN\.?w\.?\b/i, 'Northwest ' )
72
+ _address.gsub!( /\bN\.?\b/, 'North ' )
73
+ _address.gsub!( /\bE\.?\b/, 'East ' )
74
+ _address.gsub!( /\bS\.?\b/, 'South ' )
75
+ _address.gsub!( /\bW\.?\b/, 'West ' )
76
+ _address.gsub!( '.', '' )
77
+ _address.gsub!( / +/, ' ' )
78
+ _address.strip
79
+ end
80
+
81
+ def formatted_phone
82
+ if self
83
+ # remove non-digit characters
84
+ _self = self.gsub(/[\(\) -]+/, '')
85
+ # format as phone if 10 digits are left
86
+ return number_to_phone(_self, :area_code => true ) if !! (_self =~ /[0-9]{10}/)
87
+ end
88
+
89
+ self
90
+ end
91
+
92
+ def formatted_zip
93
+ return if self.blank?
94
+ self.gsub!( /[\(\) -]+/, '' )
95
+ self.size == 9 ? "#{self[0 .. 4]}-#{self[5 .. -1]}" : self
96
+ end
97
+
98
+ # Time methods
99
+
100
+ def to_12_hour_time
101
+ (self == '0' || self.blank?) ? nil : Time.parse( "#{self[0..-3]}:#{self[-2..-1]}" ).to_s( :time ).gsub(/^0/, '')
102
+ end
103
+
104
+ # URL methods
105
+
106
+ # Prefixes the given url with 'http://'.
107
+ def add_http_prefix
108
+ return if self.blank?
109
+ _uri = self.to_uri
110
+ return self if _uri.nil? || _uri.is_a?(URI::FTP) || _uri.is_a?(URI::HTTP) || _uri.is_a?(URI::HTTPS) || _uri.is_a?(URI::LDAP) || _uri.is_a?(URI::MailTo)
111
+ "http://#{self}"
112
+ end
113
+
114
+ # Returns true if a given string begins with http:// or https://.
115
+ def has_http?
116
+ !! (self =~ /^http[s]?:\/\/.+/)
117
+ end
118
+
119
+ # Returns true if a given string has a trailing slash.
120
+ def has_trailing_slash?
121
+ !! (self =~ /\/$/)
122
+ end
123
+
124
+ # Returns true if a given string refers to an HTML page.
125
+ def is_page?
126
+ !! (self =~ /\.htm[l]?$/)
127
+ end
128
+
129
+ # Returns the host from a given URL string; returns nil if the string is not a valid URL.
130
+ def to_host
131
+ _uri = self.to_uri
132
+ _uri ? _uri.host : nil
133
+ end
134
+
135
+ # Returns a URI for the given string; nil if the string is invalid.
136
+ def to_uri
137
+ begin
138
+ _uri = URI.parse self
139
+ rescue URI::InvalidURIError
140
+ _uri = nil
141
+ end
142
+
143
+ _uri
144
+ end
145
+
146
+ # Returns true if the given string is a valid URL.
147
+ def valid_http_url?
148
+ self.scan(/:\/\//).size == 1 && self.to_uri.is_a?(URI::HTTP)
149
+ end
150
+
151
+ end
152
+
153
+ # Array Extensions ===============================================================================
154
+ class Array
155
+ def mean
156
+ self.inject(0){ |sum, x| sum += x } / self.size.to_f
157
+ end
158
+
159
+ def count
160
+ self.size
161
+ end
162
+
163
+ end
164
+
165
+ # Enumerable Extensions ==========================================================================
166
+ module Enumerable
167
+ def to_histogram
168
+ inject(Hash.new(0)) { |h,x| h[x] += 1; h }
169
+ end
170
+ end
171
+
172
+ # Fixnum Extensions ==============================================================================
173
+ class Fixnum
174
+
175
+ # Given a number of seconds, convert into a string like HH:MM:SS
176
+ def to_hrs_mins_secs
177
+ _now = DateTime.now
178
+ _d = Date::day_fraction_to_time((_now + self.seconds) - _now)
179
+ "#{sprintf('%02d',_d[0])}:#{sprintf('%02d',_d[1])}:#{sprintf('%02d',_d[2])}"
180
+ end
181
+ end
182
+
183
+ # Float Extensions ===============================================================================
184
+ class Float
185
+ def to_nearest_tenth
186
+ sprintf("%.1f", self).to_f
187
+ end
188
+ end
189
+
190
+ # Hash Extensions ================================================================================
191
+ class Hash
192
+ def to_params
193
+ params = ''
194
+ stack = []
195
+
196
+ each do |k, v|
197
+ if v.is_a?(Hash)
198
+ stack << [k,v]
199
+ elsif v.is_a?(Array)
200
+ stack << [k,Hash.from_array(v)]
201
+ else
202
+ params << "#{k}=#{v}&"
203
+ end
204
+ end
205
+
206
+ stack.each do |parent, hash|
207
+ hash.each do |k, v|
208
+ if v.is_a?(Hash)
209
+ stack << ["#{parent}[#{k}]", v]
210
+ else
211
+ params << "#{parent}[#{k}]=#{v}&"
212
+ end
213
+ end
214
+ end
215
+
216
+ params.chop!
217
+ params
218
+ end
219
+
220
+ def to_sql( operator = 'AND' )
221
+ _sql = self.keys.map do |_key|
222
+ _value = self[_key].is_a?(Fixnum) ? self[_key] : "'#{self[_key]}'"
223
+ self[_key].nil? ? '1 = 1' : "#{_key} = #{_value}"
224
+ end
225
+ _sql * " #{operator} "
226
+ end
227
+
228
+ def self.from_array(array = [])
229
+ h = Hash.new
230
+ array.size.times{ |t| h[t] = array[t] }
231
+ h
232
+ end
233
+
234
+ end
235
+
236
+ # SOAP Extensions ================================================================================
237
+ class Header < SOAP::Header::SimpleHandler
238
+ def initialize(tag, value)
239
+ super(XSD::QName.new(nil, tag))
240
+ @tag = tag
241
+ @value = value
242
+ end
243
+
244
+ def on_simple_outbound
245
+ @value
246
+ end
247
+ end
248
+
249
+ class ActiveRecord::Base
250
+
251
+ #FIXME Extending AR in this way will stop working under Rails 2.3.2 for some reason.
252
+
253
+ # scope :order_by, lambda{ |col, dir| {:order => (col.blank?) ? ( (dir.blank?) ? 'id' : dir ) : "#{col} #{dir}"} }
254
+ # scope :limit, lambda { |num| { :limit => num } }
255
+
256
+ # TODO: call the column_names class method on the subclass
257
+ # named_scope :sort_by, lambda{ |col, dir| {:order => (col.blank?) ? ( (dir.blank?) ? (Client.column_names.include?('name') ? 'name' : 'id') : h(dir) ) : "#{h(col)} #{h(dir)}"} }
258
+
259
+ # Returns an array of SQL conditions suitable for use with ActiveRecord's finder.
260
+ # valid_criteria is an array of valid search fields.
261
+ # pairs is a hash of field names and values.
262
+ def self.search_conditions( valid_search_criteria, pairs, operator = 'OR' )
263
+ if valid_search_criteria.detect{ |_c| ! pairs[_c].blank? } || ! pairs[:query].blank?
264
+ _conditions = []
265
+ _or_clause = ''
266
+ _or_clause_values = []
267
+ _int_terms = {}
268
+ _text_terms = {}
269
+
270
+ # build or clause for keyword search
271
+ unless pairs[:query].blank? || ! self.respond_to?(:flattened_content)
272
+ pairs[:query].split(' ').each do |keyword|
273
+ _or_clause += 'flattened_content LIKE ? OR '
274
+ _or_clause_values << "%#{keyword}%"
275
+ end
276
+
277
+ _or_clause.gsub!( / OR $/, '')
278
+ end
279
+
280
+ # iterate across each valid search field
281
+ valid_search_criteria.each do |_field|
282
+ # build or clause for keyword search
283
+ unless pairs[:query].blank? || self.respond_to?(:flattened_content)
284
+ pairs[:query].split(' ').each do |keyword|
285
+ _or_clause += "#{_field} LIKE ? OR "
286
+ _or_clause_values << "%#{keyword}%"
287
+ end
288
+ end
289
+
290
+ # build hashes of integer and/or text search fields and values for each non-blank param
291
+ if ! pairs[_field].blank?
292
+ _field.to_s =~ /^id$|_id$|\?$/ ? _int_terms[_field.to_s.gsub('?', '')] = pairs[_field] : _text_terms[_field] = pairs[_field]
293
+ end
294
+ end
295
+
296
+ _or_clause.gsub!( / OR $/, '')
297
+
298
+ # convert the hash to parametric SQL
299
+ if _or_clause.blank?
300
+ _conditions = sql_conditions_for( _int_terms, _text_terms, nil, operator )
301
+ elsif _int_terms.keys.empty? && _text_terms.keys.empty?
302
+ _conditions = [ _or_clause ]
303
+ else
304
+ _conditions = sql_conditions_for( _int_terms, _text_terms, _or_clause, operator )
305
+ end
306
+
307
+ # add integer values
308
+ _int_terms.keys.each{ |key| _conditions << _int_terms[key] }
309
+ # add wildcard-padded values
310
+ _text_terms.keys.each{ |key| _conditions << "%#{_text_terms[key]}%" }
311
+
312
+ unless _or_clause_values.empty?
313
+ # add keywords
314
+ _conditions += _or_clause_values
315
+ end
316
+
317
+ return _conditions
318
+ else
319
+ return nil
320
+ end
321
+ end
322
+
323
+ def self.to_option_values
324
+ self.all.map{ |_x| [_x.name, _x.id] }
325
+ end
326
+
327
+ # Strips the specified attribute's value.
328
+ def strip(attribute)
329
+ value = self[attribute]
330
+ self.send("#{attribute}=", value && value.strip)
331
+ end
332
+
333
+ private
334
+
335
+ def self.sql_conditions_for( integer_fields, text_fields, or_clause = nil, operator = 'OR' )
336
+ if integer_fields.empty? && ! text_fields.empty?
337
+ [ text_fields.keys.map{ |k| k } * " LIKE ? #{operator} " + ' LIKE ?' + (or_clause ? " #{operator} #{or_clause}" : '') ]
338
+ elsif ! integer_fields.empty? && text_fields.empty?
339
+ [ integer_fields.keys.map{ |k| k } * " = ? #{operator} " + ' = ?' + (or_clause ? " #{operator} #{or_clause}" : '') ]
340
+ else
341
+ [ integer_fields.keys.map{ |k| k } * " = ? #{operator} " + " = ? #{operator} " + text_fields.keys.map{ |k| k } * " LIKE ? #{operator} " + ' LIKE ?' + (or_clause ? " #{operator} #{or_clause}" : '') ]
342
+ end
343
+ end
344
+ end
@@ -0,0 +1,383 @@
1
+ module ActionController
2
+ module Helpers
3
+
4
+ def action?( expression )
5
+ !! ( expression.class == Regexp ? controller.action_name =~ expression : controller.action_name == expression )
6
+ end
7
+
8
+ # Formats an array with HTML line breaks, or the specified delimiter.
9
+ def array_to_lines(array, delimiter = '<br />')
10
+ array.blank? ? nil : array * delimiter
11
+ end
12
+
13
+ def checkmark
14
+ %{<div class="checkmark"></div>}
15
+ end
16
+
17
+ def controller?( expression )
18
+ !! ( expression.class == Regexp ? controller.controller_name =~ expression : controller.controller_name == expression )
19
+ end
20
+
21
+ # Display CRUD icons or links, according to setting in use_crud_icons method.
22
+ #
23
+ # In application_helper.rb:
24
+ #
25
+ # def use_crud_icons
26
+ # true
27
+ # end
28
+ #
29
+ # Then use in index views like this:
30
+ #
31
+ # <td class="crud_links"><%= crud_links(my_model, 'my_model', [:show, :edit, :delete]) -%></td>
32
+ #
33
+ def crud_links(model, instance_name, actions, args={})
34
+ _html = ""
35
+ _options = args.keys.empty? ? '' : ", #{args.map{|k,v| ":#{k} => #{v}"}}"
36
+
37
+ if use_crud_icons
38
+ if actions.include?(:show)
39
+ _html << eval("link_to image_tag('/images/icons/view.png', :class => 'crud_icon'), model, :title => 'View'#{_options}")
40
+ end
41
+ if actions.include?(:edit)
42
+ _html << eval("link_to image_tag('/images/icons/edit.png', :class => 'crud_icon'), edit_#{instance_name}_path(model), :title => 'Edit'#{_options}")
43
+ end
44
+ if actions.include?(:delete)
45
+ _html << eval("link_to image_tag('/images/icons/delete.png', :class => 'crud_icon'), model, :confirm => 'Are you sure? This action cannot be undone.', :method => :delete, :title => 'Delete'#{_options}")
46
+ end
47
+ else
48
+ if actions.include?(:show)
49
+ _html << eval("link_to 'View', model, :title => 'View', :class => 'crud_link'#{_options}")
50
+ end
51
+ if actions.include?(:edit)
52
+ _html << eval("link_to 'Edit', edit_#{instance_name}_path(model), :title => 'Edit', :class => 'crud_link'#{_options}")
53
+ end
54
+ if actions.include?(:delete)
55
+ _html << eval("link_to 'Delete', model, :confirm => 'Are you sure? This action cannot be undone.', :method => :delete, :title => 'Delete', :class => 'crud_link'#{_options}")
56
+ end
57
+ end
58
+ _html
59
+ end
60
+
61
+ # Display CRUD icons or links, according to setting in use_crud_icons method.
62
+ # This method works with nested resources.
63
+ # Use in index views like this:
64
+ #
65
+ # <td class="crud_links"><%= crud_links_for_nested_resource(@my_model, my_nested_model, 'my_model', 'my_nested_model', [:show, :edit, :delete]) -%></td>
66
+ #
67
+ def crud_links_for_nested_resource(model, nested_model, model_instance_name, nested_model_instance_name, actions, args={})
68
+ _html = ""
69
+ if use_crud_icons
70
+ if actions.include?(:show)
71
+ _html << eval("link_to image_tag('/images/icons/view.png', :class => 'crud_icon'), #{model_instance_name}_#{nested_model_instance_name}_path(model, nested_model), :title => 'View'")
72
+ end
73
+
74
+ if actions.include?(:edit)
75
+ _html << eval("link_to image_tag('/images/icons/edit.png', :class => 'crud_icon'), edit_#{model_instance_name}_#{nested_model_instance_name}_path(model, nested_model), :title => 'Edit'")
76
+ end
77
+
78
+ if actions.include?(:delete)
79
+ _html << eval("link_to image_tag('/images/icons/delete.png', :class => 'crud_icon'), #{model_instance_name}_#{nested_model_instance_name}_path(model, nested_model), :method => :delete, :confirm => 'Are you sure? This action cannot be undone.', :title => 'Delete'")
80
+ end
81
+ end
82
+ _html
83
+ end
84
+
85
+ # DRY way to return a legend tag that renders correctly in all browsers. This variation allows
86
+ # for more "stuff" inside the legend tag, e.g. expand/collapse controls, without having to worry
87
+ # about escape sequences.
88
+ #
89
+ # Sample usage:
90
+ #
91
+ # <%- legend_block do -%>
92
+ # <span id="hide_or_show_backlinks" class="show_link" style="background-color: #999999;
93
+ # border: 1px solid #999999;" onclick="javascript:hide_or_show('backlinks');"></span>Backlinks (<%=
94
+ # @google_results.size -%>)
95
+ # <%- end -%>
96
+ #
97
+ def legend_block(&block)
98
+ concat content_tag(:div, capture(&block), :class => "faux_legend")
99
+ end
100
+
101
+ # DRY way to return a legend tag that renders correctly in all browsers
102
+ def legend_tag(text, args={})
103
+ _html = %{<div id="#{args[:id]}" class="faux_legend">#{text}</div>\r}
104
+ _html.gsub!(/ id=""/,'')
105
+ _html.gsub!(/ class=""/,'')
106
+ _html
107
+ end
108
+
109
+ def meta_description(content=nil)
110
+ content_for(:meta_description) { content } unless content.blank?
111
+ end
112
+
113
+ def meta_keywords(content=nil)
114
+ content_for(:meta_keywords) { content } unless content.blank?
115
+ end
116
+
117
+ def models_for_select( models, label = 'name' )
118
+ models.map{ |m| [m[label], m.id] }.sort_by{ |e| e[0] }
119
+ end
120
+
121
+ def options_for_array( a, selected = nil, prompt = select_prompt )
122
+ "<option value=''>#{prompt}</option>" + a.map{ |_e| _flag = _e[0].to_s == selected ? 'selected="1"' : ''; _e.is_a?(Array) ? "<option value=\"#{_e[0]}\" #{_flag}>#{_e[1]}</option>" : "<option>#{_e}</option>" }.to_s
123
+ end
124
+
125
+ # Create a link that is opaque to search engine spiders.
126
+ def obfuscated_link_to(path, image, label, args={})
127
+ _html = %{<form action="#{path}" method="get" class="obfuscated_link">}
128
+ _html << %{ <fieldset><input alt="#{label}" src="#{image}" type="image" /></fieldset>}
129
+ args.each{ |k,v| _html << %{ <div><input id="#{k.to_s}" name="#{k}" type="hidden" value="#{v}" /></div>} }
130
+ _html << %{</form>}
131
+ _html
132
+ end
133
+
134
+ # Wraps the given HTML in Rails' default style to highlight validation errors, if any.
135
+ def required_field_helper( model, element, html )
136
+ if model && ! model.errors.empty? && element.is_required
137
+ return content_tag( :div, html, :class => 'fieldWithErrors' )
138
+ else
139
+ return html
140
+ end
141
+ end
142
+
143
+ def select_prompt
144
+ "Select..."
145
+ end
146
+
147
+ def select_prompt_option
148
+ "<option value=''>#{select_prompt}</option>"
149
+ end
150
+
151
+ # Use on index pages to create dropdown list of filtering criteria.
152
+ # Populate the filter list using a constant in the model corresponding to named scopes.
153
+ #
154
+ # Usage:
155
+ #
156
+ # - item.rb:
157
+ #
158
+ # named_scope :active, :conditions => { :is_active => true }
159
+ # named_scope :inactive, :conditions => { :is_active => false }
160
+ #
161
+ # FILTERS = [
162
+ # {:scope => "all", :label => "All"},
163
+ # {:scope => "active", :label => "Active Only"},
164
+ # {:scope => "inactive", :label => "Inactive Only"}
165
+ # ]
166
+ #
167
+ # - items/index.html.erb:
168
+ #
169
+ # <%= select_tag_for_filter("items", @filters, params) -%>
170
+ #
171
+ # - items_controller.rb:
172
+ #
173
+ # def index
174
+ # @filters = Item::FILTERS
175
+ # if params[:show] && params[:show] != "all" && @filters.collect{|f| f[:scope]}.include?(params[:show])
176
+ # @items = eval("@items.#{params[:show]}.order_by(params[:by], params[:dir])")
177
+ # else
178
+ # @items = @items.order_by(params[:by], params[:dir])
179
+ # end
180
+ # ...
181
+ # end
182
+ #
183
+ def select_tag_for_filter(model, nvpairs, params)
184
+ return unless model && nvpairs && ! nvpairs.empty?
185
+ options = { :query => params[:query] }
186
+ _url = url_for(eval("#{model}_url(options)"))
187
+ _html = %{<label for="show">Show:</label><br />}
188
+ _html << %{<select name="show" id="show" onchange="window.location='#{_url}' + '?show=' + this.value">}
189
+ nvpairs.each do |pair|
190
+ _html << %{<option value="#{pair[:scope]}"}
191
+ if params[:show] == pair[:scope] || ((params[:show].nil? || params[:show].empty?) && pair[:scope] == "all")
192
+ _html << %{ selected="selected"}
193
+ end
194
+ _html << %{>#{pair[:label]}}
195
+ _html << %{</option>}
196
+ end
197
+ _html << %{</select>}
198
+ end
199
+
200
+ # Returns a link_to tag with sorting parameters that can be used with ActiveRecord.order_by.
201
+ #
202
+ # To use standard resources, specify the resources as a plural symbol:
203
+ # sort_link(:users, 'email', params)
204
+ #
205
+ # To use resources aliased with :as (in routes.rb), specify the aliased route as a string.
206
+ # sort_link('users_admin', 'email', params)
207
+ #
208
+ # You can override the link's label by adding a labels hash to your params in the controller:
209
+ # params[:labels] = {'user_id' => 'User'}
210
+ def sort_link(model, field, params, html_options={})
211
+ if (field.to_sym == params[:by] || field == params[:by]) && params[:dir] == "ASC"
212
+ classname = "arrow-asc"
213
+ dir = "DESC"
214
+ elsif (field.to_sym == params[:by] || field == params[:by])
215
+ classname = "arrow-desc"
216
+ dir = "ASC"
217
+ else
218
+ dir = "ASC"
219
+ end
220
+
221
+ options = {
222
+ :anchor => html_options[:anchor] || nil,
223
+ :by => field,
224
+ :dir => dir,
225
+ :query => params[:query],
226
+ :show => params[:show]
227
+ }
228
+
229
+ options[:show] = params[:show] unless params[:show].blank? || params[:show] == 'all'
230
+
231
+ html_options = {
232
+ :class => "#{classname} #{html_options[:class]}",
233
+ :style => "color: white; font-weight: #{params[:by] == field ? "bold" : "normal"}; #{html_options[:style]}",
234
+ :title => "Sort by this field"
235
+ }
236
+
237
+ field_name = params[:labels] && params[:labels][field] ? params[:labels][field] : field.titleize
238
+
239
+ _link = model.is_a?(Symbol) ? eval("#{model}_url(options)") : "/#{model}?#{options.to_params}"
240
+ link_to(field_name, _link, html_options)
241
+ end
242
+
243
+ # Tabbed interface helpers =======================================================================
244
+
245
+ # Returns formatted tabs with appropriate JS for activation. Use in conjunction with tab_body.
246
+ #
247
+ # Usage:
248
+ #
249
+ # <%- tabset do -%>
250
+ # <%= tab_tag :id => 'ppc_ads', :label => 'PPC Ads', :state => 'active' %>
251
+ # <%= tab_tag :id => 'budget' %>
252
+ # <%= tab_tag :id => 'geotargeting' %>
253
+ # <%- end -%>
254
+ #
255
+ def tabset(&proc)
256
+ concat %{
257
+ <div class="jump_links">
258
+ <ul>
259
+ }
260
+ yield
261
+ concat %{
262
+ </ul>
263
+ </div>
264
+ <br style="clear: both;" /><br />
265
+ <input type="hidden" id="show_tab" />
266
+ <script type="text/javascript">
267
+ function hide_all_tabs() { $$('.tab_block').invoke('hide'); }
268
+ function activate_tab(tab) {
269
+ $$('.tab_control').each(function(elem){ elem.className = 'tab_control'});
270
+ $('show_' + tab).className = 'tab_control active';
271
+ hide_all_tabs();
272
+ $(tab).toggle();
273
+ $('show_tab').value = tab
274
+ }
275
+ function sticky_tab() { if (location.hash) { activate_tab(location.hash.gsub('#','')); } }
276
+ Event.observe(window, 'load', function() { sticky_tab(); });
277
+ </script>
278
+ }
279
+ end
280
+
281
+ # Returns a tab body corresponding to tabs in a tabset. Make sure that the id of the tab_body
282
+ # matches the id provided to the tab_tag in the tabset block.
283
+ #
284
+ # Usage:
285
+ #
286
+ # <%- tab_body :id => 'ppc_ads', :label => 'PPC Ad Details' do -%>
287
+ # PPC ads form here.
288
+ # <%- end -%>
289
+ #
290
+ # <%- tab_body :id => 'budget' do -%>
291
+ # Budget form here.
292
+ # <%- end -%>
293
+ #
294
+ # <%- tab_body :id => 'geotargeting' do -%>
295
+ # Geotargeting form here.
296
+ # <%- end -%>
297
+ #
298
+ def tab_body(args, &proc)
299
+ concat %{<div id="#{args[:id]}" class="tab_block form_container" style="display: #{args[:display] || 'none'};">}
300
+ concat %{#{legend_tag args[:label] || args[:id].titleize }}
301
+ concat %{<a name="#{args[:id]}"></a><br />}
302
+ yield
303
+ concat %{</div>}
304
+ end
305
+
306
+ # Returns the necessary HTML for a particular tab. Use inside a tabset block.
307
+ # Override the default tab label by specifying a :label parameter.
308
+ # Indicate that the tab should be active by setting its :state to 'active'.
309
+ # (NOTE: You must define a corresponding CSS style for active tabs.)
310
+ #
311
+ # Usage:
312
+ #
313
+ # <%= tab_tag :id => 'ppc_ads', :label => 'PPC Ads', :state => 'active' %>
314
+ #
315
+ def tab_tag(args, *css_class)
316
+ %{<li id="show_#{args[:id]}" class="tab_control #{args[:state]}" onclick="window.location='##{args[:id]}'; activate_tab('#{args[:id]}');">#{args[:label] || args[:id].to_s.titleize}</li>}
317
+ end
318
+
319
+ # ================================================================================================
320
+
321
+ def tag_for_collapsible_row(obj, params)
322
+ _html = ""
323
+ if obj && obj.respond_to?(:parent) && obj.parent
324
+ _html << %{<tr class="#{obj.class.name.downcase}_#{obj.parent.id} #{params[:class]}" style="display: none; #{params[:style]}">}
325
+ else
326
+ _html << %{<tr class="#{params[:class]}" style="#{params[:style]}">}
327
+ end
328
+ _html
329
+ end
330
+
331
+ def tag_for_collapsible_row_control(obj)
332
+ _base_id = "#{obj.class.name.downcase}_#{obj.id}"
333
+ _html = %{<div id="hide_or_show_#{_base_id}" class="show_link" style="background-color: #999999; border: 1px solid #999999;" onclick="javascript:hide_or_show('#{_base_id}');"></div>}
334
+ end
335
+
336
+ # Create a set of tags for displaying a field label with inline help.
337
+ # Field label text is appended with a ? icon, which responds to a click
338
+ # by showing or hiding the provided help text.
339
+ #
340
+ # Sample usage:
341
+ #
342
+ # <%= tag_for_label_with_inline_help 'Relative Frequency', 'rel_frequency', 'Relative frequency of search traffic for this keyword across multiple search engines, as measured by WordTracker.' %>
343
+ #
344
+ # Yields:
345
+ #
346
+ # <label for="rel_frequency">Relative Frequency: <%= image_tag "/images/help_icon.png", :onclick => "$('rel_frequency_help').toggle();", :class => 'inline_icon' %></label><br />
347
+ # <div class="inline_help" id="rel_frequency_help" style="display: none;">
348
+ # <p>Relative frequency of search traffic for this keyword across multiple search engines, as measured by WordTracker.</p>
349
+ # </div>
350
+ def tag_for_label_with_inline_help( label_text, field_id, help_text )
351
+ _html = ""
352
+ _html << %{<label for="#{field_id}">#{label_text}}
353
+ _html << %{<img src="/images/icons/help_icon.png" onclick="$('#{field_id}_help').toggle();" class='inline_icon' />}
354
+ _html << %{</label><br />}
355
+ _html << %{<div class="inline_help" id="#{field_id}_help" style="display: none;">}
356
+ _html << %{<p>#{help_text}</p>}
357
+ _html << %{</div>}
358
+ _html
359
+ end
360
+
361
+ # Create a set of tags for displaying a field label followed by instructions.
362
+ # The instructions are displayed on a new line following the field label.
363
+ #
364
+ # Usage:
365
+ #
366
+ # <%= tag_for_label_with_instructions 'Status', 'is_active', 'Only active widgets will be visible to the public.' %>
367
+ #
368
+ # Yields:
369
+ #
370
+ # <label for="is_active">
371
+ # Status<br />
372
+ # <span class="instructions">Only active widgets will be visible to the public.</span>
373
+ # <label><br />
374
+ def tag_for_label_with_instructions( label_text, field_id, instructions )
375
+ _html = ""
376
+ _html << %{<label for="#{field_id}">#{label_text}}
377
+ _html << %{<span class="instructions">#{instructions}</span>}
378
+ _html << %{</label><br />}
379
+ _html
380
+ end
381
+
382
+ end
383
+ end