cck_forms 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +1 -0
- data/README.md +1 -0
- data/Rakefile +40 -0
- data/lib/cck_forms/date_time.rb +58 -0
- data/lib/cck_forms/engine.rb +23 -0
- data/lib/cck_forms/form_builder_extensions.rb +18 -0
- data/lib/cck_forms/parameter_type_class/album.rb +100 -0
- data/lib/cck_forms/parameter_type_class/base.rb +275 -0
- data/lib/cck_forms/parameter_type_class/boolean.rb +24 -0
- data/lib/cck_forms/parameter_type_class/checkboxes.rb +202 -0
- data/lib/cck_forms/parameter_type_class/date.rb +35 -0
- data/lib/cck_forms/parameter_type_class/date_range.rb +53 -0
- data/lib/cck_forms/parameter_type_class/date_time.rb +76 -0
- data/lib/cck_forms/parameter_type_class/enum.rb +53 -0
- data/lib/cck_forms/parameter_type_class/file.rb +80 -0
- data/lib/cck_forms/parameter_type_class/float.rb +20 -0
- data/lib/cck_forms/parameter_type_class/image.rb +20 -0
- data/lib/cck_forms/parameter_type_class/integer.rb +77 -0
- data/lib/cck_forms/parameter_type_class/integer_range.rb +150 -0
- data/lib/cck_forms/parameter_type_class/map.rb +259 -0
- data/lib/cck_forms/parameter_type_class/phones.rb +204 -0
- data/lib/cck_forms/parameter_type_class/string.rb +13 -0
- data/lib/cck_forms/parameter_type_class/string_collection.rb +23 -0
- data/lib/cck_forms/parameter_type_class/text.rb +18 -0
- data/lib/cck_forms/parameter_type_class/time.rb +30 -0
- data/lib/cck_forms/parameter_type_class/work_hours.rb +369 -0
- data/lib/cck_forms/version.rb +3 -0
- data/lib/cck_forms.rb +4 -0
- data/vendor/assets/javascripts/cck_forms/jquery.workhours.js +399 -0
- data/vendor/assets/javascripts/cck_forms/map.js.coffee +322 -0
- metadata +116 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
class CckForms::ParameterTypeClass::IntegerRange # Rover :)
|
2
|
+
include CckForms::ParameterTypeClass::Base
|
3
|
+
|
4
|
+
def self.name
|
5
|
+
'Диапазон между двумя целыми числами'
|
6
|
+
end
|
7
|
+
|
8
|
+
# {from: 500, to: 1000, ranges: {"300-600" => true, "601-900" => true, "901-1500" => false}}
|
9
|
+
def mongoize
|
10
|
+
value_from_form = value
|
11
|
+
return nil if value_from_form.blank?
|
12
|
+
|
13
|
+
from = value_from_form.try(:[], 'from').to_i
|
14
|
+
till = value_from_form.try(:[], 'till').to_i
|
15
|
+
|
16
|
+
db_representation = {
|
17
|
+
from: from,
|
18
|
+
till: till,
|
19
|
+
ranges: {}
|
20
|
+
}
|
21
|
+
|
22
|
+
if @extra_options[:ranges].respond_to? :each
|
23
|
+
@extra_options[:ranges].each do |range_string|
|
24
|
+
low, high = range_string.split(range_string_delimiter)
|
25
|
+
if high.to_i.to_s != high.to_s
|
26
|
+
high = Integer::MAX_32BIT
|
27
|
+
end
|
28
|
+
low, high = low.to_i, high.to_i
|
29
|
+
|
30
|
+
# -----
|
31
|
+
# [ RANGE ]
|
32
|
+
completely_in_range = (from >= low && till <= high)
|
33
|
+
|
34
|
+
# -------
|
35
|
+
# [ RANGE ]
|
36
|
+
#
|
37
|
+
# ------
|
38
|
+
# [ RANGE ]
|
39
|
+
intersects_range_partially = (from <= low && till >= low) || (from <= high && till >= high)
|
40
|
+
|
41
|
+
# -----------
|
42
|
+
# [ RANGE ]
|
43
|
+
contains_range = from < low && till > high
|
44
|
+
|
45
|
+
db_representation[:ranges][range_string] = completely_in_range || intersects_range_partially || contains_range
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
db_representation
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s(options = {})
|
53
|
+
options ||= {}
|
54
|
+
return '' if value.blank?
|
55
|
+
|
56
|
+
delimiter = options[:delimeter].presence || default_integer_range_delimiter
|
57
|
+
|
58
|
+
from = value.try(:[], 'from').to_i
|
59
|
+
till = value.try(:[], 'till').to_i
|
60
|
+
|
61
|
+
return '' if from.zero? && till.zero?
|
62
|
+
|
63
|
+
if from.zero?
|
64
|
+
"до #{till}"
|
65
|
+
elsif till.zero?
|
66
|
+
"от #{from}"
|
67
|
+
elsif from == till
|
68
|
+
from.to_s
|
69
|
+
else
|
70
|
+
[from, till].join(delimiter)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_form(form_builder, options)
|
75
|
+
set_value_in_hash options
|
76
|
+
if options.delete(:for) == :search
|
77
|
+
build_search_form(form_builder, options)
|
78
|
+
else
|
79
|
+
build_for_admin_interface_form(form_builder, options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def search(criteria, field, query)
|
84
|
+
criteria.where("#{field}.ranges.#{query}" => true)
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def build_for_admin_interface_form(form_builder, options)
|
92
|
+
delimiter = options[:delimeter].presence || ' — '
|
93
|
+
|
94
|
+
default_style = {class: 'form-control input-small'}
|
95
|
+
result = ['<div class="form-inline">']
|
96
|
+
form_builder.fields_for :value do |ff|
|
97
|
+
from_field = ff.number_field :from, options.merge(value: value.try(:[], 'from')).reverse_merge(default_style)
|
98
|
+
till_field = ff.number_field :till, options.merge(value: value.try(:[], 'till')).reverse_merge(default_style)
|
99
|
+
result << [from_field, till_field].join(delimiter).html_safe
|
100
|
+
end
|
101
|
+
result << '</div>'
|
102
|
+
result.join.html_safe
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_search_form(form_builder, options)
|
106
|
+
delimiter = options[:delimeter].presence || default_integer_range_delimiter
|
107
|
+
form_fields = []
|
108
|
+
visual_representation = options.delete(:as)
|
109
|
+
show_only = options.delete(:only)
|
110
|
+
|
111
|
+
if visual_representation == :select
|
112
|
+
klazz = options.delete :class
|
113
|
+
form_fields << form_builder.select(:value, [['', '']] + humanized_integer_ranges_for_select, options.merge(selected: options[:value]), {class: klazz} )
|
114
|
+
else
|
115
|
+
show_all_fields = !show_only
|
116
|
+
|
117
|
+
if show_all_fields or show_only == :low
|
118
|
+
form_fields << form_builder.text_field(:from, options.merge(index: 'value', value: value.try(:[], 'from')))
|
119
|
+
end
|
120
|
+
|
121
|
+
if show_all_fields or show_only == :high
|
122
|
+
form_fields << form_builder.text_field(:till, options.merge(index: 'value', value: value.try(:[], 'till')))
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
form_fields.join(delimiter).html_safe
|
127
|
+
end
|
128
|
+
|
129
|
+
def default_integer_range_delimiter
|
130
|
+
"–"
|
131
|
+
end
|
132
|
+
|
133
|
+
def humanized_integer_ranges_for_select
|
134
|
+
@extra_options[:ranges].map do |range_string|
|
135
|
+
low, high = range_string.split(range_string_delimiter)
|
136
|
+
if low.to_i.to_s != low.to_s
|
137
|
+
option_text = "до #{high}"
|
138
|
+
elsif high.to_i.to_s != high.to_s
|
139
|
+
option_text = "свыше #{low}"
|
140
|
+
else
|
141
|
+
option_text = [low, high].join(default_integer_range_delimiter)
|
142
|
+
end
|
143
|
+
[option_text, range_string]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def range_string_delimiter
|
148
|
+
/[-:\\]/
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
class CckForms::ParameterTypeClass::Map
|
2
|
+
include CckForms::ParameterTypeClass::Base
|
3
|
+
|
4
|
+
MAP_TYPE_GOOGLE = 'google'.freeze
|
5
|
+
MAP_TYPE_YANDEX = 'yandex'.freeze
|
6
|
+
DEFAULT_MAP_TYPE = MAP_TYPE_GOOGLE
|
7
|
+
|
8
|
+
mattr_accessor :map_providers
|
9
|
+
@@map_providers = [MAP_TYPE_YANDEX, MAP_TYPE_GOOGLE]
|
10
|
+
|
11
|
+
mattr_accessor :google_maps_api_key
|
12
|
+
|
13
|
+
def self.name
|
14
|
+
'Точка на карте'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Было базе: {latlon: [x, y], zoom: z}
|
18
|
+
#
|
19
|
+
# Стало в модели: {
|
20
|
+
# latitude: x,
|
21
|
+
# longitude: y,
|
22
|
+
# zoom: z
|
23
|
+
# }
|
24
|
+
def self.demongoize_value(value, parameter_type_class=nil)
|
25
|
+
value = value.to_h
|
26
|
+
value.stringify_keys!
|
27
|
+
latlon = value['latlon'] || []
|
28
|
+
|
29
|
+
latitude = value['latitude'] || latlon[0]
|
30
|
+
longitude = value['longitude'] || latlon[1]
|
31
|
+
type_of_map = value['type'] || DEFAULT_MAP_TYPE
|
32
|
+
|
33
|
+
{
|
34
|
+
'latitude' => latitude,
|
35
|
+
'longitude' => longitude,
|
36
|
+
'zoom' => value['zoom'].presence,
|
37
|
+
'type' => type_of_map
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# Было в модели: {
|
43
|
+
# latitude: x,
|
44
|
+
# longitude: y,
|
45
|
+
# zoom: z
|
46
|
+
# }
|
47
|
+
#
|
48
|
+
# Стало в базе: {latlon: [x, y], zoom: z}
|
49
|
+
def mongoize
|
50
|
+
value = self.value.is_a?(Hash) ? self.value : {}
|
51
|
+
return {
|
52
|
+
'latlon' => [value['latitude'].presence, value['longitude'].presence],
|
53
|
+
'zoom' => value['zoom'].presence,
|
54
|
+
'type' => value['type'].presence || DEFAULT_MAP_TYPE
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# Если переданы :width и :height, вызовет img_tag, иначе вернет пустую строку.
|
59
|
+
def to_s(options = {})
|
60
|
+
options ||= {}
|
61
|
+
if options[:width].to_i > 0 and options[:height].to_i > 0
|
62
|
+
return a_tag(to_s(options.except :link), options[:link]) if options[:link]
|
63
|
+
return img_tag options[:width], options[:height]
|
64
|
+
end
|
65
|
+
|
66
|
+
''
|
67
|
+
end
|
68
|
+
|
69
|
+
# Возвращает тэг IMG с картинкой карты и точкой на ней, если value не пустое (содержит координаты точки).
|
70
|
+
# См. Google/Yandex Maps Static API.
|
71
|
+
def img_tag(width, height, options = {})
|
72
|
+
map_type = value['type']
|
73
|
+
|
74
|
+
if value['latitude'].present? and value['longitude'].present?
|
75
|
+
if map_type == MAP_TYPE_GOOGLE
|
76
|
+
zoom_if_any = value['zoom'].present? ? "&zoom=#{value['zoom']}" : nil
|
77
|
+
marker_size_if_any = options[:marker_size] ? "|size:#{options[:marker_size]}" : nil
|
78
|
+
|
79
|
+
url = %Q(
|
80
|
+
http://maps.googleapis.com/maps/api/staticmap?
|
81
|
+
language=ru&
|
82
|
+
size=#{width}x#{height}&
|
83
|
+
maptype=roadmap&
|
84
|
+
markers=color:red#{marker_size_if_any}|
|
85
|
+
#{value['latitude']},#{value['longitude']}&
|
86
|
+
sensor=false
|
87
|
+
#{zoom_if_any}
|
88
|
+
).gsub(/\s+/, '')
|
89
|
+
|
90
|
+
else # yandex
|
91
|
+
zoom_if_any = value['zoom'].present? ? "&z=#{value['zoom']}" : nil
|
92
|
+
marker_size = options[:marker_size] == :large ? 'l' : 'm'
|
93
|
+
|
94
|
+
url = %Q(
|
95
|
+
http://static-maps.yandex.ru/1.x/?
|
96
|
+
l=map&
|
97
|
+
size=#{width},#{height}&
|
98
|
+
pt=#{value['longitude']},#{value['latitude']},pm2bl#{marker_size}&
|
99
|
+
#{zoom_if_any}
|
100
|
+
).gsub(/\s+/, '')
|
101
|
+
end
|
102
|
+
%Q(<img src="#{url}" width="#{width}" height="#{height}" />).html_safe
|
103
|
+
else
|
104
|
+
''
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Возвращает тэг A со ссылкой на карту с маркером объекта.
|
109
|
+
def a_tag(content, attrs)
|
110
|
+
if attrs[:href] = url
|
111
|
+
attrs_strings = []
|
112
|
+
attrs.each_pair { |name, value| attrs_strings << sprintf('%s="%s"', name, value) }
|
113
|
+
sprintf '<a %s>%s</a>', attrs_strings.join, content
|
114
|
+
else
|
115
|
+
''
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Возвращает урл на карту.
|
120
|
+
def url
|
121
|
+
if value['latitude'].present? and value['longitude'].present?
|
122
|
+
if value['type'] == MAP_TYPE_GOOGLE
|
123
|
+
sprintf(
|
124
|
+
'http://maps.google.com/maps?%s&t=m&q=%s+%s',
|
125
|
+
value['zoom'].present? ? 'z=' + value['zoom'] : '',
|
126
|
+
value['latitude'],
|
127
|
+
value['longitude']
|
128
|
+
)
|
129
|
+
else # yandex
|
130
|
+
sprintf(
|
131
|
+
'http://maps.yandex.ru/?l=map&text=%s&ll=%s%s',
|
132
|
+
[value['latitude'], value['longitude']].join(','),
|
133
|
+
[value['longitude'], value['latitude']].join(','),
|
134
|
+
value['zoom'].present? ? '&z=' + value['zoom'] : nil
|
135
|
+
)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Построим форму для карты. Представляет собой 3 спрятанных поля latitude, longitude, zoom. Затем рядом ставится ДИВ,
|
141
|
+
# на который вешается карта Гугла, и с помощью скриптов изменение полей привязывается к кликам на карте и изменению
|
142
|
+
# масштаба.
|
143
|
+
#
|
144
|
+
# 1 клик на пустом месте ставит точку (пишем в поля), старая точка при этом удаляется. Клик на точке удаляет ее
|
145
|
+
# (очищаем поля).
|
146
|
+
#
|
147
|
+
# Также, слушает событие change поля "город" (facility_page_cck_params_city_value), чтобы по известным названиям
|
148
|
+
# городов отцентровать карту на выбранном городе.
|
149
|
+
#
|
150
|
+
# options:
|
151
|
+
#
|
152
|
+
# value - тек. значение, см. self.demongoize_value
|
153
|
+
# width - ширина карты
|
154
|
+
# height - высота карты
|
155
|
+
# latitude - широта центра карты, если ничего не выбрано
|
156
|
+
# longitude - долгота центра карты, если ничего не выбрано
|
157
|
+
# zoom - масштаб карты, если ничего не выбрано
|
158
|
+
|
159
|
+
def build_form(form_builder, options)
|
160
|
+
set_value_in_hash options
|
161
|
+
|
162
|
+
options = {
|
163
|
+
width: 550,
|
164
|
+
height: 400,
|
165
|
+
latitude: 47.757581,
|
166
|
+
longitude: 67.298256,
|
167
|
+
zoom: 5,
|
168
|
+
value: {},
|
169
|
+
}.merge options
|
170
|
+
|
171
|
+
value = (options[:value].is_a? Hash) ? options[:value].stringify_keys : {}
|
172
|
+
|
173
|
+
inputs = []
|
174
|
+
id = ''
|
175
|
+
|
176
|
+
form_builder.tap do |value_builder|
|
177
|
+
id = form_builder_name_to_id value_builder
|
178
|
+
inputs << value_builder.hidden_field(:latitude, value: value['latitude'])
|
179
|
+
inputs << value_builder.hidden_field(:longitude, value: value['longitude'])
|
180
|
+
inputs << value_builder.hidden_field(:zoom, value: value['zoom'])
|
181
|
+
inputs << value_builder.hidden_field(:type, value: value['type'])
|
182
|
+
end
|
183
|
+
|
184
|
+
city_id = id.clone
|
185
|
+
city_id['map'] = 'city'
|
186
|
+
city_id += '_value'
|
187
|
+
|
188
|
+
cities_js = []
|
189
|
+
city_class = if defined?(KazakhstanCities::City) then KazakhstanCities::City elsif defined?(City) then City end
|
190
|
+
if city_class
|
191
|
+
city_class.all.each do |city|
|
192
|
+
cities_js.push [city.id, {lat: city.lat.to_f, lon: city.lon.to_f, zoom: city.zoom.to_i}]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
cities_js = Hash[cities_js].to_json
|
196
|
+
|
197
|
+
allowed_maps = @@map_providers
|
198
|
+
map_names = {'google' => 'Google', 'yandex' => 'Яндекс'}
|
199
|
+
selected_map_type = value['type'].in?(allowed_maps) ? value['type'] : allowed_maps.first
|
200
|
+
|
201
|
+
switchers = []
|
202
|
+
switchers << %Q|<div class="btn-group cck-map-switchers #{'hide' if allowed_maps.count < 2}" style="margin-top: 5px;">|
|
203
|
+
allowed_maps.map do |map|
|
204
|
+
switchers << %Q|<a class="btn btn-default #{selected_map_type == map ? 'active' : nil}" href="#" data-map-type="#{map}">#{map_names[map]}</a>|
|
205
|
+
end
|
206
|
+
switchers << '</div>'
|
207
|
+
|
208
|
+
map_html_containers = []
|
209
|
+
allowed_maps.each do |map|
|
210
|
+
map_html_containers.push %Q|<div id="#{id}_#{map}" data-id=#{id} class="map_widget" style="display: none; width: #{options[:width]}px; height: #{options[:height]}px"></div>|
|
211
|
+
end
|
212
|
+
|
213
|
+
api_key = @@google_maps_api_key.present? ? "&key=#{@@google_maps_api_key}" : nil
|
214
|
+
|
215
|
+
%Q|
|
216
|
+
<div class="map-canvas">
|
217
|
+
#{inputs.join}
|
218
|
+
|
219
|
+
<script>
|
220
|
+
var mapsReady = {google: false, yandex: false, callback: null, on: function(callback) {
|
221
|
+
this.callback = callback;
|
222
|
+
this.fireIfReady();
|
223
|
+
}, fireIfReady: function() {
|
224
|
+
if(this.google && this.yandex && this.callback) { this.callback() }
|
225
|
+
}}
|
226
|
+
|
227
|
+
function googleMapReady() { mapsReady.google = true; mapsReady.fireIfReady() }
|
228
|
+
function yandexMapReady() { mapsReady.yandex = true; mapsReady.fireIfReady() }
|
229
|
+
|
230
|
+
function loadMapScripts() {
|
231
|
+
var script;
|
232
|
+
script = document.createElement('script');
|
233
|
+
script.type = 'text/javascript';
|
234
|
+
script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=googleMapReady#{api_key}';
|
235
|
+
document.body.appendChild(script);
|
236
|
+
|
237
|
+
script = document.createElement('script');
|
238
|
+
script.type = 'text/javascript';
|
239
|
+
script.src = 'https://api-maps.yandex.ru/2.0/?coordorder=longlat&load=package.full&wizard=constructor&lang=ru-RU&onload=yandexMapReady';
|
240
|
+
document.body.appendChild(script);
|
241
|
+
}
|
242
|
+
|
243
|
+
window.onload = loadMapScripts;
|
244
|
+
</script>
|
245
|
+
|
246
|
+
<div data-map-data-source data-options='#{options.to_json}' data-id="#{id}" data-cities='#{cities_js}' data-cityid="#{city_id}" data-allowed-maps='#{allowed_maps.to_json}' style="width: #{options[:width]}px; height: #{options[:height]}px">
|
247
|
+
#{map_html_containers.join}
|
248
|
+
</div>
|
249
|
+
|
250
|
+
#{switchers.join}
|
251
|
+
</div>
|
252
|
+
|
|
253
|
+
end
|
254
|
+
|
255
|
+
def to_diff_value(options = {})
|
256
|
+
demongoize_value!
|
257
|
+
img_tag(64, 64, marker_size: :small)
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
class CckForms::ParameterTypeClass::Phones
|
2
|
+
include CckForms::ParameterTypeClass::Base
|
3
|
+
|
4
|
+
MIN_PHONES_IN_FORM = Rails.application.config.cck_forms.phones.min_phones_in_form
|
5
|
+
MOBILE_CODES = Rails.application.config.cck_forms.phones.mobile_codes
|
6
|
+
PREFIX = Rails.application.config.cck_forms.phones.prefix
|
7
|
+
NUMBER_PARTS_GLUE = Rails.application.config.cck_forms.phones.number_parts_glue
|
8
|
+
|
9
|
+
def self.name
|
10
|
+
'Телефоны'
|
11
|
+
end
|
12
|
+
|
13
|
+
# Проверяет входной массив на наличие телефонов (хэша с ключами prefix, code и number).
|
14
|
+
# На выходе выдает очищенный массив хэшей с такими ключами, пропуская пустые телефоны.
|
15
|
+
#
|
16
|
+
# Было в модели: [{prefix: '+7'}, {code: ' 123 ', number: '1234567', zzz: ''}]
|
17
|
+
#
|
18
|
+
# Стало в базе: [{prefix: '', code: '123', number: '1234567'}]
|
19
|
+
def mongoize
|
20
|
+
value = self.value
|
21
|
+
return [] unless value.respond_to? :each
|
22
|
+
|
23
|
+
value = value.values if value.is_a? Hash
|
24
|
+
|
25
|
+
result = []
|
26
|
+
value.each do |phone|
|
27
|
+
phone = {} if phone.blank? or !(phone.is_a? Hash)
|
28
|
+
phone = blank_phone.merge phone.stringify_keys
|
29
|
+
|
30
|
+
phone['prefix'] = phone['prefix'].strip
|
31
|
+
phone['code'] = clean_numbers(phone['code'].to_s)
|
32
|
+
phone['number'] = clean_numbers(phone['number'].to_s)
|
33
|
+
|
34
|
+
if phone['code'].present? or phone['number'].present?
|
35
|
+
result << {
|
36
|
+
'prefix' => phone['prefix'],
|
37
|
+
'code' => phone['code'],
|
38
|
+
'number' => phone['number'],
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
# Просто приводит телефоны в базе в соответствие формату.
|
47
|
+
def self.demongoize_value(value, parameter_type_class=nil)
|
48
|
+
if value
|
49
|
+
value.map do |phone|
|
50
|
+
phone = phone.stringify_keys!
|
51
|
+
{
|
52
|
+
'prefix' => phone['prefix'],
|
53
|
+
'code' => phone['code'],
|
54
|
+
'number' => phone['number'],
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Строит форму для заполнения телефонов. В ней будет минимум MIN_PHONES_IN_FORM телефонов, если их меньше, остальные
|
61
|
+
# будут добавлены пустые.
|
62
|
+
#
|
63
|
+
# Также, если заполнены все MIN_PHONES_IN_FORM, добавит 1 пустое поле, чтобы можно было добавить новый телефон.
|
64
|
+
#
|
65
|
+
# Возвращает ХТМЛ.
|
66
|
+
def build_form(form_builder, options)
|
67
|
+
set_value_in_hash options
|
68
|
+
value = options[:value].presence
|
69
|
+
value = [] unless !value.blank? and value.is_a? Array
|
70
|
+
|
71
|
+
result = value.map { |phone| build_single_form(form_builder, phone) }
|
72
|
+
|
73
|
+
[1, CckForms::ParameterTypeClass::Phones::MIN_PHONES_IN_FORM - result.length].max.times { result << build_single_form(form_builder, {}) }
|
74
|
+
|
75
|
+
id = form_builder_name_to_id form_builder
|
76
|
+
sprintf '<div id="%s">%s</div>%s', id, result.join, script(id)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Строит форму для 1 телефона из значения вида {prefix: '', code: '', number: ''}
|
80
|
+
def build_single_form(form_builder, phone)
|
81
|
+
phone = {} unless phone.is_a? Hash
|
82
|
+
phone = blank_phone.merge phone
|
83
|
+
|
84
|
+
phone_form = []
|
85
|
+
|
86
|
+
form_builder.fields_for(:value, index: '') do |phone_builder|
|
87
|
+
phone_form << phone_builder.text_field(:prefix, class: 'input-tiny form-control', value: phone['prefix'])
|
88
|
+
phone_form << phone_builder.text_field(:code, class: 'input-mini form-control', value: phone['code'])
|
89
|
+
phone_form << phone_builder.text_field(:number, class: 'input-small form-control', value: phone['number'])
|
90
|
+
end
|
91
|
+
|
92
|
+
sprintf '<p class="form-inline">%s — %s — %s</p>', phone_form[0], phone_form[1], phone_form[2]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Возвращает 1 пустой телефон (хэш вида {prefix: '+7', code: '', number: ''}). Так сказать, эталон.
|
96
|
+
def blank_phone
|
97
|
+
{
|
98
|
+
'prefix' => PREFIX,
|
99
|
+
'code' => '',
|
100
|
+
'number' => '',
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def script(id)
|
105
|
+
<<HTML
|
106
|
+
<script type="text/javascript">
|
107
|
+
$(function() {
|
108
|
+
var $phones = $("##{id}");
|
109
|
+
var doTimes = #{CckForms::ParameterTypeClass::Phones::MIN_PHONES_IN_FORM};
|
110
|
+
|
111
|
+
var createPhone = function() {
|
112
|
+
var $newPhone = $phones.children("p:last").clone();
|
113
|
+
$newPhone.children("input").each(function() {
|
114
|
+
var $this = $(this);
|
115
|
+
var isPrefix = $this.prop('name').match(/\\[prefix\\]$/);
|
116
|
+
$this.val(isPrefix ? "#{blank_phone['prefix']}" : '');
|
117
|
+
var index = $this.prop("id").match(/value_([0-9]+)_/);
|
118
|
+
if(!index) {
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
index = index[1] * 1;
|
122
|
+
$this.prop("id", $this.prop("id").replace(index, index + 1));
|
123
|
+
$this.prop("name", $this.prop("name").replace(index, index + 1));
|
124
|
+
})
|
125
|
+
$phones.children("p:last").after($newPhone);
|
126
|
+
}
|
127
|
+
|
128
|
+
$phones.append('<a href="#" class="add_more">Добавить еще телефонов</a>');
|
129
|
+
$phones.children(".add_more").click(function() {
|
130
|
+
for(var i = 0; i < doTimes; ++ i) {
|
131
|
+
createPhone();
|
132
|
+
}
|
133
|
+
return false;
|
134
|
+
})
|
135
|
+
});
|
136
|
+
</script>
|
137
|
+
HTML
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_html(options = {})
|
141
|
+
phones_list = []
|
142
|
+
(value || []).each do |phone|
|
143
|
+
if phone['number'] && clean_numbers(phone['number'].to_s).present?
|
144
|
+
if phone['prefix'].present? || phone['code'].present?
|
145
|
+
prefix = phone['prefix'].present? ? "<span class=\"phone-prefix\">#{phone['prefix']}</span>" : ''
|
146
|
+
code = phone['code'].present? ? "<span class=\"phone-code\">#{phone['code']}</span>" : ''
|
147
|
+
start = sprintf(phone['code'].in?(MOBILE_CODES) ? '<span class="phone-mobile-prefix">%s(%s)</span>' : '<span class="phone-city-prefix">%s(%s)</span>', prefix, code)
|
148
|
+
else
|
149
|
+
start = ''
|
150
|
+
end
|
151
|
+
|
152
|
+
number = split_number(clean_numbers(phone['number'])).join(NUMBER_PARTS_GLUE)
|
153
|
+
phones_list << sprintf('<span class="phone">%s<span class="phone-number">%s</span></span>', start, number)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
phones_list = phones_list.take(options[:limit]) if options[:limit]
|
158
|
+
|
159
|
+
if options[:as_list]
|
160
|
+
phones_list
|
161
|
+
else
|
162
|
+
phones_list.join(', ').html_safe
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def to_s(options = {})
|
167
|
+
HTML::FullSanitizer.new.sanitize to_html(options)
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
# 123-45-67 asdasd -> 1234567
|
175
|
+
def clean_numbers(number)
|
176
|
+
number.gsub /\D/, ''
|
177
|
+
end
|
178
|
+
|
179
|
+
# 1234567 -> 123 45 67 с тэгами
|
180
|
+
def split_number(number)
|
181
|
+
if number.length > 4
|
182
|
+
tokens = []
|
183
|
+
|
184
|
+
# перевернем строку и разобъем на пары
|
185
|
+
number.reverse.scan(/.(?:.|$)/) do |token|
|
186
|
+
token.reverse!
|
187
|
+
if token.length == 1
|
188
|
+
tokens.last.prepend token
|
189
|
+
else
|
190
|
+
tokens << token
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# сольем все обратно
|
195
|
+
tokens.reverse!
|
196
|
+
tokens.tap do |tokens|
|
197
|
+
tokens.map! { |token| yield token } if block_given?
|
198
|
+
end
|
199
|
+
else
|
200
|
+
yield number if block_given?
|
201
|
+
[number]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CckForms::ParameterTypeClass::String
|
2
|
+
include CckForms::ParameterTypeClass::Base
|
3
|
+
|
4
|
+
def self.name
|
5
|
+
'Строка'
|
6
|
+
end
|
7
|
+
|
8
|
+
def build_form(form_builder, options)
|
9
|
+
set_value_in_hash options
|
10
|
+
attrs = @extra_options.slice(:maxlength, :pattern)
|
11
|
+
form_builder.text_field :value, {class: 'form-control'}.merge(attrs).merge(options)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class CckForms::ParameterTypeClass::StringCollection
|
2
|
+
include CckForms::ParameterTypeClass::Base
|
3
|
+
|
4
|
+
def self.name
|
5
|
+
'Массив строк'
|
6
|
+
end
|
7
|
+
|
8
|
+
def mongoize
|
9
|
+
value.split "\r\n" if value.is_a? String
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.demongoize_value(value, parameter_type_class=nil)
|
13
|
+
value = [value] if value.is_a? String
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_form(form_builder, options)
|
18
|
+
set_value_in_hash options
|
19
|
+
options[:value] = value.join("\r\n") if value
|
20
|
+
|
21
|
+
form_builder.text_area :value, {cols: 50, rows: 5, class: 'form-control'}.merge(options)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CckForms::ParameterTypeClass::Text
|
2
|
+
include CckForms::ParameterTypeClass::Base
|
3
|
+
|
4
|
+
def self.name
|
5
|
+
'Текст'
|
6
|
+
end
|
7
|
+
|
8
|
+
def build_form(form_builder, options)
|
9
|
+
set_value_in_hash options
|
10
|
+
form_builder.text_area :value, {cols: 50, rows: 5, class: 'form-control'}.merge(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_diff_value(options = {})
|
14
|
+
to_html.presence.try do |html|
|
15
|
+
"<div class='well well-small'>#{html}</div>".html_safe
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|