campfire_logic 1.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Capfile +4 -0
- data/Gemfile +29 -0
- data/Gemfile.local +27 -0
- data/README.rdoc +17 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/app/controllers/application_controller.rb +5 -0
- data/app/controllers/directory_controller.rb +65 -0
- data/app/controllers/locations_controller.rb +65 -0
- data/app/controllers/services_controller.rb +46 -0
- data/app/helpers/application_helper.rb +27 -0
- data/app/models/google_maps_geocoder.rb +108 -0
- data/app/models/locale.rb +204 -0
- data/app/models/location.rb +216 -0
- data/app/models/location_import.rb +48 -0
- data/app/models/service.rb +27 -0
- data/app/models/zip_code.rb +48 -0
- data/app/models/zip_code_import.rb +19 -0
- data/app/views/directory/_search_form.html.erb +4 -0
- data/app/views/directory/_show_children.html.erb +29 -0
- data/app/views/directory/_show_location.html.erb +14 -0
- data/app/views/directory/search.html.erb +31 -0
- data/app/views/directory/show.html.erb +32 -0
- data/app/views/layouts/application.html.erb +49 -0
- data/app/views/locations/_form.html.erb +69 -0
- data/app/views/locations/edit.html.erb +3 -0
- data/app/views/locations/export.erb +4 -0
- data/app/views/locations/import.html.erb +13 -0
- data/app/views/locations/index.html.erb +37 -0
- data/app/views/locations/new.html.erb +3 -0
- data/app/views/locations/show.html.erb +70 -0
- data/app/views/services/_form.html.erb +29 -0
- data/app/views/services/edit.html.erb +3 -0
- data/app/views/services/index.html.erb +25 -0
- data/app/views/services/new.html.erb +3 -0
- data/app/views/services/show.html.erb +15 -0
- data/app/views/shared/_location.html.erb +18 -0
- data/app/views/shared/_map.html.erb +2 -0
- data/app/views/shared/_nav_tabs.html.erb +5 -0
- data/autotest/discover.rb +1 -0
- data/campfire_logic.gemspec +208 -0
- data/config/application.rb +44 -0
- data/config/boot.rb +13 -0
- data/config/cucumber.yml +10 -0
- data/config/deploy.rb +40 -0
- data/config/environment.rb +6 -0
- data/config/environments/development.rb +28 -0
- data/config/environments/production.rb +49 -0
- data/config/environments/test.rb +35 -0
- data/config/initializers/campfire_logic.rb +3 -0
- data/config/initializers/metric_fu.rb +9 -0
- data/config/initializers/secret_token.rb +7 -0
- data/config/initializers/session_store.rb +8 -0
- data/config/locales/en.yml +5 -0
- data/config/mongoid.yml +25 -0
- data/config/routes.rb +96 -0
- data/config.ru +4 -0
- data/db/seeds.rb +5 -0
- data/db/zip_codes.txt +42742 -0
- data/doc/google_maps_response.rb +56 -0
- data/features/admin_manages_locations.feature +10 -0
- data/features/customer_browses_directory.feature +25 -0
- data/features/customer_searches_directory.feature +29 -0
- data/features/step_definitions/directory_steps.rb +22 -0
- data/features/step_definitions/location_steps.rb +10 -0
- data/features/step_definitions/web_steps.rb +211 -0
- data/features/support/env.rb +31 -0
- data/features/support/paths.rb +33 -0
- data/features/support/selectors.rb +39 -0
- data/init.rb +1 -0
- data/lib/campfire_logic/engine.rb +7 -0
- data/lib/campfire_logic/railtie.rb +10 -0
- data/lib/campfire_logic.rb +31 -0
- data/lib/tasks/campfire_logic.rake +7 -0
- data/lib/tasks/cucumber.rake +71 -0
- data/public/404.html +26 -0
- data/public/422.html +26 -0
- data/public/500.html +26 -0
- data/public/favicon.ico +0 -0
- data/public/images/icons/collapsed.gif +0 -0
- data/public/images/icons/delete.png +0 -0
- data/public/images/icons/drag.png +0 -0
- data/public/images/icons/edit.png +0 -0
- data/public/images/icons/expanded.gif +0 -0
- data/public/images/icons/help_icon.png +0 -0
- data/public/images/icons/link_icon.png +0 -0
- data/public/images/icons/move.png +0 -0
- data/public/images/icons/move_white.png +0 -0
- data/public/images/icons/note.png +0 -0
- data/public/images/icons/note_white.png +0 -0
- data/public/images/icons/notification_icon_sprite.png +0 -0
- data/public/images/icons/spinner.gif +0 -0
- data/public/images/icons/view.png +0 -0
- data/public/images/icons/warning.png +0 -0
- data/public/images/icons/warning_2.png +0 -0
- data/public/images/icons/warning_box.png +0 -0
- data/public/images/icons/warning_icon.png +0 -0
- data/public/images/icons/warning_white.png +0 -0
- data/public/images/layout/arrow_asc.png +0 -0
- data/public/images/layout/arrow_desc.png +0 -0
- data/public/images/layout/black_bar.png +0 -0
- data/public/images/layout/branding.png +0 -0
- data/public/images/layout/button_bg.png +0 -0
- data/public/images/layout/content_left_bg.png +0 -0
- data/public/images/layout/content_right_bg.png +0 -0
- data/public/images/layout/footer_bg.png +0 -0
- data/public/images/layout/h2_bg.png +0 -0
- data/public/images/layout/h2_bg_for_table.png +0 -0
- data/public/images/layout/header_bg_grey.png +0 -0
- data/public/images/layout/header_bg_purple.png +0 -0
- data/public/images/layout/legend_bg.png +0 -0
- data/public/images/layout/text_field_bg.jpg +0 -0
- data/public/images/layout/text_field_error_bg.png +0 -0
- data/public/images/layout/th_bg.png +0 -0
- data/public/images/layout/th_bg_selected.png +0 -0
- data/public/images/rails.png +0 -0
- data/public/index.html +9 -0
- data/public/javascripts/application.js +2 -0
- data/public/javascripts/controls.js +965 -0
- data/public/javascripts/dragdrop.js +974 -0
- data/public/javascripts/effects.js +1123 -0
- data/public/javascripts/prototype.js +6001 -0
- data/public/javascripts/rails.js +175 -0
- data/public/robots.txt +6 -0
- data/public/sample-locations.xls +1 -0
- data/public/stylesheets/.gitkeep +0 -0
- data/public/stylesheets/application.css +682 -0
- data/public/stylesheets/core.css +1147 -0
- data/public/stylesheets/core_ie.css +52 -0
- data/public/stylesheets/csshover3.htc +14 -0
- data/script/cucumber +10 -0
- data/script/rails +6 -0
- data/spec/controllers/directory_controller_spec.rb +11 -0
- data/spec/controllers/services_controller_spec.rb +62 -0
- data/spec/models/google_maps_geocoder_spec.rb +62 -0
- data/spec/models/locale_spec.rb +64 -0
- data/spec/models/location_import_spec.rb +41 -0
- data/spec/models/location_spec.rb +195 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/test-locations.xls +1 -0
- 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,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>© <%= 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,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' -%>
|