cck_forms 3.0.0

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