cck_forms 3.0.0

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 (32) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -0
  3. data/README.md +1 -0
  4. data/Rakefile +40 -0
  5. data/lib/cck_forms/date_time.rb +58 -0
  6. data/lib/cck_forms/engine.rb +23 -0
  7. data/lib/cck_forms/form_builder_extensions.rb +18 -0
  8. data/lib/cck_forms/parameter_type_class/album.rb +100 -0
  9. data/lib/cck_forms/parameter_type_class/base.rb +275 -0
  10. data/lib/cck_forms/parameter_type_class/boolean.rb +24 -0
  11. data/lib/cck_forms/parameter_type_class/checkboxes.rb +202 -0
  12. data/lib/cck_forms/parameter_type_class/date.rb +35 -0
  13. data/lib/cck_forms/parameter_type_class/date_range.rb +53 -0
  14. data/lib/cck_forms/parameter_type_class/date_time.rb +76 -0
  15. data/lib/cck_forms/parameter_type_class/enum.rb +53 -0
  16. data/lib/cck_forms/parameter_type_class/file.rb +80 -0
  17. data/lib/cck_forms/parameter_type_class/float.rb +20 -0
  18. data/lib/cck_forms/parameter_type_class/image.rb +20 -0
  19. data/lib/cck_forms/parameter_type_class/integer.rb +77 -0
  20. data/lib/cck_forms/parameter_type_class/integer_range.rb +150 -0
  21. data/lib/cck_forms/parameter_type_class/map.rb +259 -0
  22. data/lib/cck_forms/parameter_type_class/phones.rb +204 -0
  23. data/lib/cck_forms/parameter_type_class/string.rb +13 -0
  24. data/lib/cck_forms/parameter_type_class/string_collection.rb +23 -0
  25. data/lib/cck_forms/parameter_type_class/text.rb +18 -0
  26. data/lib/cck_forms/parameter_type_class/time.rb +30 -0
  27. data/lib/cck_forms/parameter_type_class/work_hours.rb +369 -0
  28. data/lib/cck_forms/version.rb +3 -0
  29. data/lib/cck_forms.rb +4 -0
  30. data/vendor/assets/javascripts/cck_forms/jquery.workhours.js +399 -0
  31. data/vendor/assets/javascripts/cck_forms/map.js.coffee +322 -0
  32. 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 &mdash; %s &mdash; %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