campfire_logic 1.1.7

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 (142) hide show
  1. data/Capfile +4 -0
  2. data/Gemfile +29 -0
  3. data/Gemfile.local +27 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +43 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/application_controller.rb +5 -0
  8. data/app/controllers/directory_controller.rb +65 -0
  9. data/app/controllers/locations_controller.rb +65 -0
  10. data/app/controllers/services_controller.rb +46 -0
  11. data/app/helpers/application_helper.rb +27 -0
  12. data/app/models/google_maps_geocoder.rb +108 -0
  13. data/app/models/locale.rb +204 -0
  14. data/app/models/location.rb +216 -0
  15. data/app/models/location_import.rb +48 -0
  16. data/app/models/service.rb +27 -0
  17. data/app/models/zip_code.rb +48 -0
  18. data/app/models/zip_code_import.rb +19 -0
  19. data/app/views/directory/_search_form.html.erb +4 -0
  20. data/app/views/directory/_show_children.html.erb +29 -0
  21. data/app/views/directory/_show_location.html.erb +14 -0
  22. data/app/views/directory/search.html.erb +31 -0
  23. data/app/views/directory/show.html.erb +32 -0
  24. data/app/views/layouts/application.html.erb +49 -0
  25. data/app/views/locations/_form.html.erb +69 -0
  26. data/app/views/locations/edit.html.erb +3 -0
  27. data/app/views/locations/export.erb +4 -0
  28. data/app/views/locations/import.html.erb +13 -0
  29. data/app/views/locations/index.html.erb +37 -0
  30. data/app/views/locations/new.html.erb +3 -0
  31. data/app/views/locations/show.html.erb +70 -0
  32. data/app/views/services/_form.html.erb +29 -0
  33. data/app/views/services/edit.html.erb +3 -0
  34. data/app/views/services/index.html.erb +25 -0
  35. data/app/views/services/new.html.erb +3 -0
  36. data/app/views/services/show.html.erb +15 -0
  37. data/app/views/shared/_location.html.erb +18 -0
  38. data/app/views/shared/_map.html.erb +2 -0
  39. data/app/views/shared/_nav_tabs.html.erb +5 -0
  40. data/autotest/discover.rb +1 -0
  41. data/campfire_logic.gemspec +208 -0
  42. data/config/application.rb +44 -0
  43. data/config/boot.rb +13 -0
  44. data/config/cucumber.yml +10 -0
  45. data/config/deploy.rb +40 -0
  46. data/config/environment.rb +6 -0
  47. data/config/environments/development.rb +28 -0
  48. data/config/environments/production.rb +49 -0
  49. data/config/environments/test.rb +35 -0
  50. data/config/initializers/campfire_logic.rb +3 -0
  51. data/config/initializers/metric_fu.rb +9 -0
  52. data/config/initializers/secret_token.rb +7 -0
  53. data/config/initializers/session_store.rb +8 -0
  54. data/config/locales/en.yml +5 -0
  55. data/config/mongoid.yml +25 -0
  56. data/config/routes.rb +96 -0
  57. data/config.ru +4 -0
  58. data/db/seeds.rb +5 -0
  59. data/db/zip_codes.txt +42742 -0
  60. data/doc/google_maps_response.rb +56 -0
  61. data/features/admin_manages_locations.feature +10 -0
  62. data/features/customer_browses_directory.feature +25 -0
  63. data/features/customer_searches_directory.feature +29 -0
  64. data/features/step_definitions/directory_steps.rb +22 -0
  65. data/features/step_definitions/location_steps.rb +10 -0
  66. data/features/step_definitions/web_steps.rb +211 -0
  67. data/features/support/env.rb +31 -0
  68. data/features/support/paths.rb +33 -0
  69. data/features/support/selectors.rb +39 -0
  70. data/init.rb +1 -0
  71. data/lib/campfire_logic/engine.rb +7 -0
  72. data/lib/campfire_logic/railtie.rb +10 -0
  73. data/lib/campfire_logic.rb +31 -0
  74. data/lib/tasks/campfire_logic.rake +7 -0
  75. data/lib/tasks/cucumber.rake +71 -0
  76. data/public/404.html +26 -0
  77. data/public/422.html +26 -0
  78. data/public/500.html +26 -0
  79. data/public/favicon.ico +0 -0
  80. data/public/images/icons/collapsed.gif +0 -0
  81. data/public/images/icons/delete.png +0 -0
  82. data/public/images/icons/drag.png +0 -0
  83. data/public/images/icons/edit.png +0 -0
  84. data/public/images/icons/expanded.gif +0 -0
  85. data/public/images/icons/help_icon.png +0 -0
  86. data/public/images/icons/link_icon.png +0 -0
  87. data/public/images/icons/move.png +0 -0
  88. data/public/images/icons/move_white.png +0 -0
  89. data/public/images/icons/note.png +0 -0
  90. data/public/images/icons/note_white.png +0 -0
  91. data/public/images/icons/notification_icon_sprite.png +0 -0
  92. data/public/images/icons/spinner.gif +0 -0
  93. data/public/images/icons/view.png +0 -0
  94. data/public/images/icons/warning.png +0 -0
  95. data/public/images/icons/warning_2.png +0 -0
  96. data/public/images/icons/warning_box.png +0 -0
  97. data/public/images/icons/warning_icon.png +0 -0
  98. data/public/images/icons/warning_white.png +0 -0
  99. data/public/images/layout/arrow_asc.png +0 -0
  100. data/public/images/layout/arrow_desc.png +0 -0
  101. data/public/images/layout/black_bar.png +0 -0
  102. data/public/images/layout/branding.png +0 -0
  103. data/public/images/layout/button_bg.png +0 -0
  104. data/public/images/layout/content_left_bg.png +0 -0
  105. data/public/images/layout/content_right_bg.png +0 -0
  106. data/public/images/layout/footer_bg.png +0 -0
  107. data/public/images/layout/h2_bg.png +0 -0
  108. data/public/images/layout/h2_bg_for_table.png +0 -0
  109. data/public/images/layout/header_bg_grey.png +0 -0
  110. data/public/images/layout/header_bg_purple.png +0 -0
  111. data/public/images/layout/legend_bg.png +0 -0
  112. data/public/images/layout/text_field_bg.jpg +0 -0
  113. data/public/images/layout/text_field_error_bg.png +0 -0
  114. data/public/images/layout/th_bg.png +0 -0
  115. data/public/images/layout/th_bg_selected.png +0 -0
  116. data/public/images/rails.png +0 -0
  117. data/public/index.html +9 -0
  118. data/public/javascripts/application.js +2 -0
  119. data/public/javascripts/controls.js +965 -0
  120. data/public/javascripts/dragdrop.js +974 -0
  121. data/public/javascripts/effects.js +1123 -0
  122. data/public/javascripts/prototype.js +6001 -0
  123. data/public/javascripts/rails.js +175 -0
  124. data/public/robots.txt +6 -0
  125. data/public/sample-locations.xls +1 -0
  126. data/public/stylesheets/.gitkeep +0 -0
  127. data/public/stylesheets/application.css +682 -0
  128. data/public/stylesheets/core.css +1147 -0
  129. data/public/stylesheets/core_ie.css +52 -0
  130. data/public/stylesheets/csshover3.htc +14 -0
  131. data/script/cucumber +10 -0
  132. data/script/rails +6 -0
  133. data/spec/controllers/directory_controller_spec.rb +11 -0
  134. data/spec/controllers/services_controller_spec.rb +62 -0
  135. data/spec/models/google_maps_geocoder_spec.rb +62 -0
  136. data/spec/models/locale_spec.rb +64 -0
  137. data/spec/models/location_import_spec.rb +41 -0
  138. data/spec/models/location_spec.rb +195 -0
  139. data/spec/rcov.opts +2 -0
  140. data/spec/spec_helper.rb +12 -0
  141. data/spec/test-locations.xls +1 -0
  142. metadata +361 -0
@@ -0,0 +1,216 @@
1
+ Stateflow.persistence = :mongoid
2
+
3
+ class Location
4
+ include CampfireLogic::Base
5
+ include Mongoid::Document
6
+ include Mongoid::Timestamps
7
+ include Stateflow
8
+
9
+ # Mongo Config ===================================================================================
10
+ field :name
11
+ field :address1
12
+ field :address2
13
+ field :city
14
+ field :state
15
+ field :state_long_name
16
+ field :zip
17
+ field :country
18
+ field :country_long_name
19
+ field :email
20
+ field :fax
21
+ field :phone
22
+ field :toll_free
23
+ field :hours
24
+ field :active, :type => Boolean
25
+ field :location_number
26
+ field :meta_description
27
+ field :accuracy, :type => Integer
28
+ field :address_status
29
+ field :lat_lng, :type => Array
30
+ field :validated_address1
31
+ field :validated_city
32
+ field :validated_zip
33
+ index [[ :lat_lng, Mongo::GEO2D ]], :min => -200, :max => 200
34
+ references_one :locale, :dependent => :destroy
35
+ references_and_referenced_in_many :services, :inverse_of => :locations
36
+
37
+ # Callbacks ======================================================================================
38
+
39
+ # Macros =========================================================================================
40
+ attr_reader :geocoder
41
+ trimmed_fields :address1, :address2, :city, :email, :fax, :location_number, :name, :phone, :state, :zip
42
+ validates_presence_of :address1, :city, :state
43
+
44
+ # State machine ==================================================================================
45
+ stateflow do
46
+ state_column :address_status
47
+ initial :new
48
+
49
+ state :new, :partial_match, :validated
50
+
51
+ # Approves this location's partially-matched address, marking it as "validated".
52
+ event :approve_address do
53
+ transitions :from => :partial_match, :to => :validated
54
+ end
55
+
56
+ # Clears this location's address, marking it as "new".
57
+ event :clear_address do
58
+ transitions :from => any, :to => :new
59
+ end
60
+
61
+ # Validates this location's address by geocoding. Partial matches are flagged; exact matches are flagged as "validated".
62
+ event :validate_address do
63
+ transitions :from => any, :to => [:new, :partial_match, :validated], :decide => :geocode
64
+ end
65
+ end
66
+
67
+ # Instance methods: Overrides ====================================================================
68
+ def address1=(value)
69
+ clear_geocoding if self.address1 != value
70
+ self[:address1] = value
71
+ end
72
+
73
+ def city=(value)
74
+ clear_geocoding if self.city != value
75
+ self[:city] = value
76
+ end
77
+
78
+ def state=(value)
79
+ clear_geocoding if self.state != value
80
+ self[:state] = value
81
+ end
82
+
83
+ # Instance methods: Addressing ===================================================================
84
+
85
+ # Returns this location's address for geocoding. It omits the second address field to simplify post-processing.
86
+ def geocoding_address
87
+ [address1, city, state].select{|s| ! s.blank?} * ' '
88
+ end
89
+
90
+ # Returns this location's street address, combining the two address fields with the specified delimiter. Latter defaults to a space.
91
+ def street_address( delimiter = ' ' )
92
+ [address1, address2].select{|s| ! s.blank?} * delimiter
93
+ end
94
+
95
+ # Instance methods: Geocoding ====================================================================
96
+
97
+ # Geocodes this location.
98
+ def geocode
99
+ return :new if self.geocoding_address.blank?
100
+
101
+ unless @geocoder
102
+ @geocoder = GoogleMapsGeocoder.new(self.geocoding_address)
103
+ self.country, self.country_long_name, self[:state], self.state_long_name = @geocoder.country_short_name, @geocoder.country_long_name, @geocoder.state_short_name, @geocoder.state_long_name
104
+ self.lat_lng = [@geocoder.lat, @geocoder.lng]
105
+ audit_address
106
+ end
107
+
108
+ @geocoder.partial_match? ? :partial_match : :validated
109
+ rescue SocketError
110
+ Rails.logger.error "Can't geocode without a network connection!"
111
+ :new
112
+ rescue RuntimeError
113
+ Rails.logger.error "Can't geocode!"
114
+ :new
115
+ end
116
+
117
+ # Returns true if this location has a latitude and longitude.
118
+ def geocoded?
119
+ self.lat_lng.is_a?(Array)
120
+ end
121
+
122
+ # Returns true if the specified address field matches the geocoder's results.
123
+ def matches_geocoder?(attribute)
124
+ @geocoder.blank? || self.send(attribute) == @geocoder.send(GOOGLE_MAPS_GEOCODER_FIELDS_BY_LOCATION_FIELD[attribute])
125
+ end
126
+
127
+ # Returns the n nearest locations.
128
+ def nearby_locations(n = 5)
129
+ Location.near(:lat_lng => self.lat_lng).limit(n + 1).select{ |l| l != self }
130
+ end
131
+
132
+ # Instance methods: Locales ======================================================================
133
+
134
+ def localize
135
+ self.save
136
+ self.locale ? update_locales : create_locales
137
+ self.save
138
+ end
139
+
140
+ # Returns this location's city locale.
141
+ def city_locale
142
+ self.locale.parent
143
+ end
144
+
145
+ # Returns this location's state locale.
146
+ def state_locale
147
+ self.city_locale.parent
148
+ end
149
+
150
+ private
151
+
152
+ GOOGLE_MAPS_GEOCODER_FIELDS_BY_LOCATION_FIELD = {
153
+ :address1 => :formatted_street_address,
154
+ :city => :city,
155
+ :zip => :postal_code
156
+ }.freeze
157
+
158
+ # Compares this location's address with the geocoder's results.
159
+ def audit_address
160
+ if @geocoder.partial_match?
161
+ GOOGLE_MAPS_GEOCODER_FIELDS_BY_LOCATION_FIELD.keys.each do |field|
162
+ # set "validated_" field to geocoder's result
163
+ self.send("validated_#{field}=", @geocoder.send(GOOGLE_MAPS_GEOCODER_FIELDS_BY_LOCATION_FIELD[field])) unless self.matches_geocoder?(field)
164
+ end
165
+ end
166
+ end
167
+
168
+ # Clears all addressing data set by the geocoder.
169
+ def clear_geocoding
170
+ @geocoder = nil
171
+ self.country, self.country_long_name, self.lat_lng, self.state_long_name, self.validated_address1, self.validated_city, self.validated_zip = nil
172
+ clear_address
173
+ end
174
+
175
+ # Creates a locale tree for this location.
176
+ def create_locales
177
+ # Find or create a state locale for this location
178
+ _state = Locale.find_or_create_state_for(self)
179
+
180
+ # Find or create a city locale for this location
181
+ _city = _state.children.to_a.detect{ |c| c.name =~ /#{self.city}/i } || (sleep 0.25; _state.children.create(:name => self.city))
182
+
183
+ # Find or create a locale for this location
184
+ self.locale = _city.children.to_a.detect{ |c| c.name =~ /#{self.name}/i } || (sleep 0.25; _city.children.create(:name => self.name, :skip_geocoding => true))
185
+ self.locale.save
186
+ end
187
+
188
+ # Updates the locale tree for this location.
189
+ def update_locales
190
+ # update location
191
+ self.locale.name = self.name
192
+ self.locale.save
193
+
194
+ # does its city locale have other locations?
195
+ if self.city_locale.children.count > 1
196
+ # create a new city
197
+ self.locale.parent = self.state_locale.children.create(:name => self.city)
198
+ else
199
+ # update the city
200
+ self.city_locale.name = self.city
201
+ end
202
+
203
+ # does its state locale have other cities?
204
+ if self.state_locale.children.count > 1
205
+ # find or create a state
206
+ self.city_locale.parent = Locale.find_or_create_state_for(self)
207
+ else
208
+ # update the state
209
+ self.state_locale.abbreviation = self.state
210
+ self.state_locale.name = self.state_long_name
211
+ self.state_locale.save
212
+ end
213
+
214
+ self.city_locale.save
215
+ end
216
+ end
@@ -0,0 +1,48 @@
1
+ class LocationImport < ScaffoldLogic::DataImport
2
+ def self.save(upload)
3
+ # create file path
4
+ _path = File.join('tmp', upload['datafile'].original_filename)
5
+
6
+ # write file
7
+ File.open(_path, 'wb') { |f| f.write(upload['datafile'].read) }
8
+
9
+ self.run(_path)
10
+ end
11
+
12
+ def self.test(path='public/sample-locations.xls')
13
+ self.run(path)
14
+ end
15
+
16
+ def process_row
17
+ @required_fields ||= [:'location #']
18
+
19
+ if valid_row? && _location = Location.find_or_initialize_by(:location_number => row_map[@required_fields.first])
20
+ COLUMNS_BY_MODEL_FIELDS.keys.each do |_field|
21
+ _content = eval(COLUMNS_BY_MODEL_FIELDS[_field])
22
+ _location.send("#{_field}=", _content) unless _content.blank?
23
+ end
24
+
25
+ _location.validate_address!
26
+ _location.localize
27
+ sleep 0.33
28
+ else
29
+ @errors << "Row #{@row_index}: Missing at least one required field: #{@required_fields * ', '}!"
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ COLUMNS_BY_MODEL_FIELDS = {
36
+ 'location_number' => 'row_map[:"location #"]',
37
+ 'name' => 'row_map[:"name"]',
38
+ 'address1' => 'row_map[:"address 1"]',
39
+ 'address2' => 'row_map[:"address 2"]',
40
+ 'city' => 'row_map[:"city"]',
41
+ 'state' => 'row_map[:"state"]',
42
+ 'zip' => 'row_map[:"postal code"]',
43
+ 'country' => 'row_map[:"country code"]',
44
+ 'phone' => 'row_map[:"phone"]',
45
+ 'email' => 'row_map[:"email"]',
46
+ 'meta_description' => 'row_map[:"description"]'
47
+ }
48
+ end
@@ -0,0 +1,27 @@
1
+ class Service
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ # Attributes =====================================================================================
6
+ field :name
7
+ field :description
8
+
9
+ # Indices ========================================================================================
10
+
11
+ # Constants ======================================================================================
12
+
13
+ # Scopes =========================================================================================
14
+
15
+ # Relationships ==================================================================================
16
+ references_and_referenced_in_many :locations, :inverse_of => :services
17
+
18
+ # Behavior =======================================================================================
19
+
20
+ # Callbacks ======================================================================================
21
+
22
+ # Validations ====================================================================================
23
+
24
+ # Class methods ==================================================================================
25
+
26
+ # Instance methods ===============================================================================
27
+ end
@@ -0,0 +1,48 @@
1
+ class ZipCode
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ # Attributes =====================================================================================
6
+ field :zip
7
+ field :city
8
+ field :state
9
+ field :county
10
+ field :lat_lng, :type => Array
11
+ index [[ :lat_lng, Mongo::GEO2D ]], :min => -200, :max => 200
12
+
13
+ # Indices ========================================================================================
14
+
15
+ # Constants ======================================================================================
16
+
17
+ # Scopes =========================================================================================
18
+
19
+ # Relationships ==================================================================================
20
+
21
+ # Behavior =======================================================================================
22
+
23
+ # Callbacks ======================================================================================
24
+ before_save :geocode
25
+
26
+ # Validations ====================================================================================
27
+
28
+ # Class methods ==================================================================================
29
+
30
+ # Instance methods ===============================================================================
31
+
32
+ # Geocodes this zip code.
33
+ def geocode
34
+ return if geocoded?
35
+
36
+ unless @geocoder
37
+ @geocoder = GoogleMapsGeocoder.new(self.zip)
38
+ self.city, self.state, self.county, self.lat_lng = @geocoder.city, @geocoder.state_short_name, @geocoder.county, [@geocoder.lat, @geocoder.lng], @geocoder.state_long_name
39
+ end
40
+ rescue SocketError
41
+ Rails.logger.error "Can't geocode without a network connection!"
42
+ end
43
+
44
+ # Returns true if this location has a latitude and longitude.
45
+ def geocoded?
46
+ self.lat_lng.is_a?(Array)
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ class ZipCodeImport < ScaffoldLogic::DataImport
2
+ def self.save(upload)
3
+ # create file path
4
+ _path = File.join('tmp', upload['datafile'].original_filename)
5
+
6
+ # write file
7
+ File.open(_path, 'wb') { |f| f.write(upload['datafile'].read) }
8
+
9
+ self.run(_path)
10
+ end
11
+
12
+ def self.test(path='db/zip_codes.txt')
13
+ self.run(path)
14
+ end
15
+
16
+ def process_row
17
+ zip = ZipCode.create :zip => row_map[:zip], :city => row_map[:city], :state => row_map[:state], :county => row_map[:zip_class], :lat_lng => [row_map[:latitude].to_f, row_map[:longitude].to_f]
18
+ end
19
+ end
@@ -0,0 +1,4 @@
1
+ <%= form_tag "/#{CampfireLogic.directory_slug}/search", :method => :get, :id => 'search-form' do -%>
2
+ <%= text_field_tag 'criteria', params[:criteria], :id => 'search-input', :maxlength => 40 -%>
3
+ <%= submit_tag 'Search' -%>
4
+ <%- end -%>
@@ -0,0 +1,29 @@
1
+ <%-
2
+ subtitle = " - #{@locale.location ? @locale.location.name : @locale.name}" unless @locale.planet?
3
+ set_title "Location Directory#{subtitle}"
4
+ -%>
5
+
6
+ <br style="clear: both;" />
7
+ <br style="clear: both;" />
8
+
9
+ <%- unless @locale.children.blank? -%>
10
+ <%- if @locale.root? -%>
11
+ <h2>Select a State/Province</h2>
12
+ <%- elsif @locale.country? -%>
13
+ <h2>Select a State</h2>
14
+ <%- elsif @locale.state? -%>
15
+ <h2>Select a City</h2>
16
+ <%- else -%>
17
+ <h2>Select a Location</h2>
18
+ <%- end -%>
19
+
20
+ <div>
21
+ <%- @locale.sorted_children.in_groups(2, false).each do |group| -%>
22
+ <ul style="float: left; margin-right: 1em; width: 40%;">
23
+ <%- group.each do |_child| -%>
24
+ <li><%= link_to _child.name, _child.to_url -%></li>
25
+ <%- end -%>
26
+ </ul>
27
+ <%- end -%>
28
+ </div><br style="clear: both;" />
29
+ <%- end -%>
@@ -0,0 +1,14 @@
1
+ <%- set_title "Location Directory - #{ @location.name }" -%>
2
+
3
+ <%= render 'shared/location' -%><br style="clear: both;" />
4
+ <br style="clear: both;" />
5
+
6
+ <h2>Nearby Locations</h2>
7
+
8
+ <%- @location.nearby_locations.in_groups(2, false).each do |group| -%>
9
+ <ul style="float: left; margin-right: 1em; width: 40%;">
10
+ <% group.each do |@location| %>
11
+ <li><%= render 'shared/location' -%></li>
12
+ <% end %>
13
+ </ul>
14
+ <%- end -%>
@@ -0,0 +1,31 @@
1
+ <%- set_title 'Location Directory - Search' -%>
2
+
3
+ <div id="map_canvas" style="border: solid black 1px; width: 100%; height: 300px"></div><br />
4
+
5
+ <%= render 'search_form' -%><br style="clear: both;" />
6
+ <br style="clear: both;" />
7
+
8
+ <div id="directions_panel"></div>
9
+
10
+ <%- if @locations.empty? -%>
11
+ <p>No matching locations found. Please try again.</p>
12
+ </em></p>
13
+ <%- else -%>
14
+ <h2><%= controller.searching_by_zip? ? 'Locations nearest' : "#{@locations.size} locations match" -%> "<%= params[:criteria] -%>"</h2>
15
+
16
+ <%- for @location in @locations -%>
17
+ <%= render 'shared/location' -%>
18
+ <%- end -%>
19
+ <%- end -%>
20
+
21
+ <br style="clear: both;" />
22
+
23
+ <%= render :partial => 'shared/map', :locals => {:locations => @locations, :place => Locale.us.first} -%>
24
+
25
+ <script type="text/javascript">
26
+ initializeMap();
27
+
28
+ $('search-form').addEventListener('submit', function (e) {
29
+ window.location = '<%= directory_search_root_url -%>/' + $('search-input').value;
30
+ }, false);
31
+ </script>
@@ -0,0 +1,32 @@
1
+ <div id="map_canvas" style="border: solid black 1px; width: 100%; height: 300px"></div><br />
2
+
3
+ <%= render 'search_form' -%>
4
+
5
+ <div id="directions_panel" style="margin-bottom: 1em;"></div>
6
+
7
+ <%- if Locale.count <= 1 -%>
8
+ <%- set_title 'Location Directory' -%>
9
+
10
+ <p>No locations found. Why don't you <%= link_to 'import', import_locations_path -%> some?</p>
11
+ <%- else -%>
12
+ <%- if @locale.location? -%>
13
+ <%= render :partial => 'show_location' -%>
14
+ <%- else -%>
15
+ <%= render :partial => 'show_children' -%>
16
+ <%- end -%>
17
+ <%- end -%>
18
+
19
+ <br style="clear: both;" />
20
+
21
+ <%= render :partial => 'shared/map', :locals => {:locations => @locale.locations, :place => @locale.root? ? Locale.us.first : @locale} -%>
22
+
23
+ <script type="text/javascript">
24
+ initializeMap();
25
+
26
+ // change search-form's action so its query string can be converted to a URL
27
+ $('search-form').action = 'javascript:return(false);';
28
+
29
+ $('search-form').addEventListener('submit', function (e) {
30
+ window.location = '<%= directory_search_root_url -%>/' + $('search-input').value;
31
+ }, false);
32
+ </script>
@@ -0,0 +1,49 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml">
3
+ <head>
4
+ <title><%= yield(:title) -%></title>
5
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
6
+ <meta name="description" content="" />
7
+ <meta name="keywords" content="" />
8
+ <link rel="canonical" href="<%= request.url -%>" />
9
+ <link rel="shortcut icon" href="/favicon.ico" />
10
+ <%= stylesheet_link_tag 'core', 'application', :cache => true -%>
11
+ <!--[if IE]>
12
+ <%= stylesheet_link_tag 'core_ie', :media => :all -%>
13
+ <![endif]-->
14
+ <%= javascript_include_tag :defaults, :cache => true -%>
15
+ <%= csrf_meta_tag -%>
16
+ </head>
17
+ <body>
18
+ <div id="container">
19
+ <div id="header">
20
+ <div id="branding"></div>
21
+ <div id="navigation"><%= render :partial => 'shared/nav_tabs' -%></div>
22
+ </div>
23
+ <div id="page_header">
24
+ <h1><%= yield(:page_title) -%></h1>
25
+ <div id="breadcrumbs"><%= breadcrumbs -%></div>
26
+ </div>
27
+ <div id="content_area">
28
+ <div id="washout" style="display:none;"></div>
29
+
30
+ <%- flash.each do |name, msg| -%>
31
+ <%- if name == :notice -%>
32
+ <%= content_tag :div, msg, :id => "flash_#{name}", :class => "flash_#{name}", :style => "display:none;" -%>
33
+ <script type="text/javascript">
34
+ $('washout').toggle();
35
+ $('flash_notice').toggle();
36
+ <%= visual_effect(:fade, 'flash_notice', :duration => 1.0, :delay => 1.0).html_safe -%>
37
+ <%= visual_effect(:fade, 'washout', :duration => 1.0, :delay => 1.0).html_safe -%>
38
+ </script>
39
+ <%- else -%>
40
+ <%= content_tag :div, msg, :id => "flash_#{name}", :class => "flash_#{name}" -%>
41
+ <%- end -%>
42
+ <%- end -%>
43
+ <%= yield -%>
44
+ <br style="clear: both;" />
45
+ </div>
46
+ <div id="footer"><p>&copy; <%= Time.zone.now.year -%> SEO Logic. All rights reserved.</p></div>
47
+ </div>
48
+ </body>
49
+ </html>
@@ -0,0 +1,69 @@
1
+ <%= form_for(@location) do |f| -%>
2
+ <%- if @location.errors.any? -%>
3
+ <div id="error_explanation">
4
+ <h2>Please correct the following errors:</h2>
5
+ <%- @location.errors.to_a.in_groups(2, false).each do |group| -%>
6
+ <ul>
7
+ <%- group.each do |msg| -%>
8
+ <li><%= msg -%></li>
9
+ <%- end -%>
10
+ </ul>
11
+ <%- end -%>
12
+ <br style="clear: both;" />
13
+ </div>
14
+ <br style="clear: both;" />
15
+ <%- end -%>
16
+
17
+ <fieldset class="form_container">
18
+ <%= legend_tag 'About This Location' -%>
19
+ <div class="form_column">
20
+ <%= f.text_field :name -%>
21
+ <%= f.check_box :active, :inline_label => 'Yes' -%>
22
+ </div>
23
+
24
+ <div class="form_column">
25
+ <div id="map_canvas" style="border: solid black 1px; width: 100%; height: 200px"></div>
26
+ </div>
27
+ </fieldset>
28
+
29
+ <fieldset class="form_container">
30
+ <%= legend_tag 'Address' -%>
31
+ <div class="form_column">
32
+ <%= f.text_field :address1 -%>
33
+ <%= f.text_field :address2 -%>
34
+ <%= f.text_field :city -%>
35
+ <%= f.text_field :state -%>
36
+ <%= f.text_field :zip -%>
37
+ </div>
38
+ <div class="form_column">
39
+ <%= f.text_field :phone -%>
40
+ <%= f.text_field :fax -%>
41
+ <%= f.text_field :email -%>
42
+ </div>
43
+ </fieldset>
44
+
45
+ <fieldset class="form_container">
46
+ <%= legend_tag 'Services Offered' -%>
47
+ <%- i = 0 -%>
48
+ <%- Service.all.to_a.in_groups(2, false) do |column| -%>
49
+ <div class="form_column">
50
+ <%- column.each do |service| -%>
51
+ <fieldset>
52
+ <%= check_box_tag 'location[service_ids][]', service.id, @location.services.include?(service), :id => "service_#{i}" -%>
53
+ <%= service.name -%>
54
+ </fieldset>
55
+ <%- i += 1 -%>
56
+ <%- end -%>
57
+ </div>
58
+ <%- end -%>
59
+ </fieldset>
60
+
61
+ <%= f.submit 'Save', :class => 'button' -%>
62
+ <%= link_to 'Cancel', @location, :class => 'button' -%>
63
+ <%- end -%>
64
+
65
+ <%= render :partial => 'shared/map', :locals => {:place => @location.locale} -%>
66
+
67
+ <script type="text/javascript">
68
+ initializeMap();
69
+ </script>
@@ -0,0 +1,3 @@
1
+ <%- set_title "Editing #{@location.name}" -%>
2
+
3
+ <%= render :partial => 'form' -%>
@@ -0,0 +1,4 @@
1
+ Location # Name Address 1 Address 2 City State Postal Code Country Code Phone Email Description
2
+ <%- @locations.each do |l| -%>
3
+ <%= l.location_number -%> <%= l.name -%> <%= l.address1 -%> <%= l.address2 -%> <%= l.city -%> <%= l.state -%> <%= l.zip -%> <%= l.country -%> <%= l.phone -%> <%= l.email -%> <%= l.meta_description %>
4
+ <%- end -%>
@@ -0,0 +1,13 @@
1
+ <%- set_title 'Import Locations' -%>
2
+
3
+ <%= form_tag upload_locations_path, :method => :post, :multipart => true do -%>
4
+ <fieldset class="form_container">
5
+ <p>To import locations, upload the import file below. Make sure the file follows this <%= link_to 'format', '/sample-locations.xls' -%>: it must be tab-delimited and the column names must match exactly. Otherwise, your data won't be processed correctly.</p>
6
+
7
+ <fieldset>
8
+ <%= file_field 'upload', 'datafile' -%>
9
+ </fieldset>
10
+ </fieldset>
11
+
12
+ <%= submit_tag 'Upload', :class => 'button' -%>
13
+ <%- end -%>
@@ -0,0 +1,37 @@
1
+ <%- set_title 'Locations' -%>
2
+
3
+ <%- unless @errors.blank? -%>
4
+ <%= @errors * '<br />' -%>
5
+ <%- end -%>
6
+
7
+ <%- if @locations.empty? -%>
8
+ <p><em>No locations found. Why don't you <%= link_to 'import', import_locations_path -%> some?
9
+ </em></p>
10
+ <%- else -%>
11
+ <table class="standard">
12
+ <tr>
13
+ <th><%= sort_link('locations', 'location_number', params) -%></th>
14
+ <th><%= sort_link('locations', 'name', params) -%></th>
15
+ <th>Address</th>
16
+ <th>URL</th>
17
+ <th> </th>
18
+ </tr>
19
+
20
+ <%- for location in @locations -%>
21
+ <tr class="<%= cycle('odd', 'even') -%>">
22
+ <td><%= link_to location.location_number, location -%></td>
23
+ <td><%= link_to location.name, location -%></td>
24
+ <td><%= location.geocoding_address -%></td>
25
+ <td>
26
+ <%- if location.locale -%>
27
+ <%= link_to location.locale.to_url, location.locale.to_url -%>
28
+ <%- end -%>
29
+ </td>
30
+ <td class="crud_links"><%= crud_links(location, [:show, :edit, :destroy]) -%></td>
31
+ </tr>
32
+ <%- end -%>
33
+ </table>
34
+ <%- end -%>
35
+
36
+ <%= link_to 'Import', import_locations_path, :class => 'button' -%>
37
+ <%= link_to 'Export', export_locations_path, :class => 'button' -%>
@@ -0,0 +1,3 @@
1
+ <%- set_title 'Create a New Location' -%>
2
+
3
+ <%= render :partial => 'form' -%>