campfire_logic 1.1.7

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