bull 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ #$LOAD_PATH.unshift '..'
2
+ require 'eventmachine'
3
+ require 'em-websocket'
4
+ require 'rethinkdb'
5
+ require 'time'
6
+ require_relative 'mreport'
7
+
8
+ def start app_controller, key_path
9
+ puts Time.now
10
+ #MReport.load_reports
11
+
12
+ $r = RethinkDB::RQL.new
13
+ conn = $r.connect()
14
+
15
+ EM.run do
16
+ EM::WebSocket.run(:host => "0.0.0.0",
17
+ :port => 3000,
18
+ :secure => true,
19
+ :tls_options => {
20
+ :private_key_file => File.join(key_path, 'privateKey.key'), # "../../privateKey.key",
21
+ :cert_chain_file => File.join(key_path, 'certificate.crt') #"../../certificate.crt"
22
+ }
23
+ ) do |ws|
24
+ controller = nil
25
+
26
+ ws.onopen { |handshake| controller = app_controller.new ws, conn}
27
+
28
+ ws.onmessage do |msg|
29
+ begin
30
+ controller.notify msg
31
+ rescue Exception => e
32
+ puts 'message: ', e.message
33
+ puts 'trace: ', e.backtrace.inspect
34
+ end
35
+ end
36
+
37
+ ws.onclose { controller.close; controller = nil }
38
+
39
+ ws.onerror { |e| puts "Error: #{e.message}"}
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,10 @@
1
+ def symbolize_keys obj
2
+ if obj.is_a? Hash
3
+ return obj.inject({}) {|memo, (k, v)| memo[k.to_sym]=symbolize_keys(v); memo}
4
+ end
5
+ if obj.is_a? Array
6
+ return obj.map {|x| symbolize_keys x}
7
+ end
8
+ obj
9
+ end
10
+
@@ -0,0 +1,18 @@
1
+ module ClassesInput
2
+ def valid_class
3
+ return '' if params.valid.nil?
4
+ if params.valid
5
+ 'input-successful'
6
+ else
7
+ 'input-incorrect'
8
+ end
9
+ end
10
+
11
+ def dirty_class
12
+ if params.dirty
13
+ 'input-dirty'
14
+ else
15
+ ''
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,796 @@
1
+ require 'reactive-ruby'
2
+ require 'set'
3
+ require_relative 'reactive_var'
4
+ require_relative 'utils'
5
+ require_relative 'bcaptcha'
6
+ require_relative 'ui_common'
7
+ require 'time'
8
+
9
+ class DisplayList < React::Component::Base
10
+
11
+ before_mount do
12
+ state.docs! []
13
+ @predicate_id = nil
14
+ end
15
+
16
+ def watch_(name, *args)
17
+ #reactives = args.pop
18
+ reactives = args.select{|v| v.is_a? RVar}
19
+ @rvs = reactive(*reactives) do
20
+ clear
21
+ $controller.stop_watch(@predicate_id) if @predicate_id != nil
22
+ args_ = args.collect do |arg|
23
+ if arg.is_a? RVar
24
+ arg.value
25
+ else
26
+ arg
27
+ end
28
+ end
29
+ #if args_.empty?
30
+ # @predicate_id = $controller.watch(name) {|data| consume data}
31
+ #else
32
+ @predicate_id = $controller.watch(name, *args_) {|data| consume data}
33
+ #end
34
+ end
35
+ end
36
+
37
+ def consume data
38
+ if data.nil?
39
+ clear
40
+ return
41
+ end
42
+ docs = state.docs.dup
43
+ if data['new_val'] == nil
44
+ index = docs.index {|x| x['id'] == data['old_val']['id']}
45
+ docs.delete_at index
46
+ elsif data['old_val'] == nil
47
+ docs << data['new_val']
48
+ else
49
+ index = docs.index {|x| x['id'] == data['old_val']['id']}
50
+ doc = docs.fetch index
51
+ doc.merge! data['new_val']
52
+ end
53
+ state.docs! sort(docs)
54
+ end
55
+
56
+ def clear
57
+ state.docs! []
58
+ end
59
+
60
+ def sort docs
61
+ docs
62
+ end
63
+
64
+ before_unmount do
65
+ $controller.stop_watch @predicate_id if @predicate_id != nil
66
+ @rvs.each_pair {|k, v| v.remove k} if @rvs
67
+ end
68
+ end
69
+
70
+ class DisplayDoc < React::Component::Base
71
+
72
+ before_mount do
73
+ @predicate_id = nil
74
+ end
75
+
76
+ def watch_ selected
77
+ @rvs = reactive(selected) do
78
+ value = selected.value
79
+ clear
80
+ $controller.stop_watch(@predicate_id) if @predicate_id != nil
81
+ @predicate_id = $controller.watch(@@table, value) do |data|
82
+ clear
83
+ data['new_val'].each {|k, v| state.__send__(k+'!', v)} if !(data.nil? || data['new_val'].nil?)
84
+ end
85
+ end
86
+ end
87
+
88
+ before_unmount do
89
+ $controller.stop_watch @predicate_id if @predicate_id != nil
90
+ @rvs.each_pair {|k, v| v.remove k} if @rvs
91
+ end
92
+ end
93
+
94
+ module AbstractStringInput
95
+ include ClassesInput
96
+ def render
97
+ span do
98
+ input(placeholder: params.placeholder, class: valid_class + ' ' + dirty_class,
99
+ type: type_attr, value: params.value){}.on(:change) do |event|
100
+ params.on_change event.target.value
101
+ end.on(:keyDown) do |event|
102
+ if event.key_code == 13
103
+ params.on_enter.call event.target.value
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ class StringInput < React::Component::Base
111
+ include AbstractStringInput
112
+
113
+ param :on_change, type: Proc
114
+ param :value, type: String
115
+ param :placeholder
116
+ param :on_enter
117
+ param :valid
118
+ param :dirty
119
+
120
+ def type_attr
121
+ :text
122
+ end
123
+ end
124
+
125
+ class PasswordInput < React::Component::Base
126
+ include AbstractStringInput
127
+
128
+ param :on_change, type: Proc
129
+ param :value, type: String
130
+ param :placeholder
131
+ param :on_enter
132
+ param :valid
133
+ param :dirty
134
+
135
+ def type_attr
136
+ :password
137
+ end
138
+ end
139
+
140
+ class MultiLineInput < React::Component::Base
141
+ param :on_change, type: Proc
142
+ param :on_enter
143
+ param :value
144
+ param :placeholder
145
+ param :valid
146
+ param :dirty
147
+
148
+ include ClassesInput
149
+
150
+ def render
151
+ textarea(placeholder: params.placeholder, class: valid_class + ' ' + dirty_class,
152
+ value: params.value){}.on(:change) do |event|
153
+ params.on_change event.target.value
154
+ end#.on(:keyDown) do |event|
155
+ # if event.key_code == 13
156
+ # params.on_enter.call event.target.value
157
+ # end
158
+ #end
159
+ end
160
+ end
161
+
162
+ =begin
163
+ class DateInput < React::Component::Base
164
+ param :on_change, type: Proc
165
+ param :value
166
+ param :format
167
+ param :valid
168
+ param :dirty
169
+
170
+ include ClassesInput
171
+
172
+ before_mount do
173
+ state.value! ''
174
+ state.year! ''
175
+ end
176
+
177
+ def date(value, year)
178
+ begin # opal: undefined method `strptime' for Time
179
+ Time.strptime(value+year, params.format+'%Y'){|y| y < 100 ? (y >= 69 ? y + 1900 : y + 2000) : y}
180
+ rescue
181
+ nil
182
+ end
183
+ end
184
+
185
+
186
+ def render
187
+ if params.value.nil?
188
+ value = state.value
189
+ year = state.year
190
+ else
191
+ value = params.value.format params.format
192
+ year = params.value.year.to_s
193
+ end
194
+ span do
195
+ input(class: valid_class + ' ' + dirty_class, value: value).on(:change) do |event|
196
+ state.value! event.target.value
197
+ params.on_change date(event.target.value, year)
198
+ end
199
+ input(value: year).on(:change) do |event|
200
+ state.year! event.target.value
201
+ params.on_change date(value, event.target.value)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ =end
207
+
208
+ module AbstractNumeric
209
+ include ClassesInput
210
+
211
+ def format val
212
+ val.to_s
213
+ end
214
+
215
+ def render
216
+ if parse(state.value) != params.value
217
+ value = params.value.to_s
218
+ else
219
+ value = state.value
220
+ end
221
+
222
+ span do
223
+ input(placeholder: params.placeholder, class: valid_class + ' ' + dirty_class, type: :text, value: format(value)){}.on(:change) do |event|
224
+ state.value! event.target.value
225
+ if event.target.value == ''
226
+ params.on_change nil
227
+ else
228
+ update_state event
229
+ end
230
+ end.on(:keyDown) do |event|
231
+ if event.key_code == 13
232
+ params.on_enter.call event.target.value
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ class IntegerInput < React::Component::Base
240
+ include AbstractNumeric
241
+
242
+ param :on_change, type: Proc
243
+ param :value, type: Integer
244
+ param :valid
245
+ param :on_enter
246
+ param :placeholder
247
+ param :dirty
248
+
249
+ def parse val
250
+ begin
251
+ Integer(val)
252
+ rescue
253
+ nil
254
+ end
255
+ end
256
+
257
+ def update_state event
258
+ params.on_change parse(event.target.value)
259
+ end
260
+ end
261
+
262
+ def format_integer value
263
+ path = value.to_s.reverse.split(/(\d\d\d)/).select{|v| v != ''}
264
+ if path[-1] == '-'
265
+ path.pop
266
+ '-' + path.join(',').reverse
267
+ else
268
+ path.join(',').reverse
269
+ end
270
+ end
271
+
272
+ def format_float value
273
+ e, d = value.to_s.split('.')
274
+ d = '' if value.to_s.end_with?('.')
275
+ return '' if e.nil?
276
+ v = format_integer e
277
+ if d.nil?
278
+ v
279
+ else
280
+ v + '.' + d
281
+ end
282
+ end
283
+
284
+ class IntegerCommaInput < React::Component::Base
285
+ include AbstractNumeric
286
+
287
+ param :on_change, type: Proc
288
+ param :value, type: Integer
289
+ param :valid
290
+ param :on_enter
291
+ param :placeholder
292
+ param :dirty
293
+
294
+ def format value
295
+ format_integer value
296
+ end
297
+
298
+ def parse val
299
+ begin
300
+ Integer(val.gsub(',', ''))
301
+ rescue
302
+ nil
303
+ end
304
+ end
305
+
306
+ def update_state event
307
+ params.on_change parse(event.target.value)
308
+ end
309
+ end
310
+
311
+ class FloatInput < React::Component::Base
312
+ include AbstractNumeric
313
+
314
+ param :on_change, type: Proc
315
+ param :value, type: Float
316
+ param :valid
317
+ param :on_enter
318
+ param :placeholder
319
+ param :dirty
320
+
321
+ def parse val
322
+ begin
323
+ Float(val)
324
+ rescue
325
+ nil
326
+ end
327
+ end
328
+
329
+ def update_state event
330
+ params.on_change parse(event.target.value)
331
+ end
332
+ end
333
+
334
+ class FloatCommaInput < React::Component::Base
335
+ include AbstractNumeric
336
+
337
+ param :on_change, type: Proc
338
+ param :value, type: Float
339
+ param :valid
340
+ param :on_enter
341
+ param :placeholder
342
+ param :dirty
343
+
344
+ def format value
345
+ format_float value
346
+ end
347
+
348
+ def parse val
349
+ begin
350
+ Float(val.gsub(',', ''))
351
+ rescue
352
+ nil
353
+ end
354
+ end
355
+
356
+ def update_state event
357
+ params.on_change parse(event.target.value)
358
+ end
359
+ end
360
+
361
+ class CheckInput < React::Component::Base
362
+ param :value
363
+ param :on_change
364
+
365
+ def render
366
+ input(type: :checkbox, checked: params.value).on(:change){params.on_change.call !params.value}
367
+ end
368
+ end
369
+
370
+ class RadioInput < React::Component::Base
371
+ param :value
372
+ param :values
373
+ param :name
374
+ param :on_change
375
+
376
+ def render
377
+ span do
378
+ params.values.each do |v|
379
+ input(type: :radio, name: params.name, value: v, checked: v == params.value){v}.on(:change){params.on_change.call v}
380
+ end
381
+ end
382
+ end
383
+ end
384
+
385
+ class HashInput < React::Component::Base
386
+ param :value
387
+ param :on_change
388
+
389
+ before_mount do
390
+ state.key! ''
391
+ state.value! ''
392
+ end
393
+
394
+ def render
395
+ span do
396
+ input(placeholder: 'key', value: state.key).on(:change){|event| state.key! event.target.value}
397
+ input(placeholder: 'value',value: state.value).on(:change){|event| state.value! event.target.value}
398
+ button{'add'}.on(:click) do
399
+ hsh = params.value.dup
400
+ hsh[state.key] = state.value
401
+ params.on_change.call hsh
402
+ state.key! ''
403
+ state.value! ''
404
+ end
405
+ table do
406
+ tr do
407
+ th{'key'}
408
+ th{'value'}
409
+ th{' '}
410
+ end
411
+ params.value.each_pair do |k, v|
412
+ tr do
413
+ td{k}
414
+ td{v}
415
+ td{i(class: 'fa fa-times')}.on(:click) do
416
+ hsh = params.value.dup
417
+ hsh.delete k
418
+ params.on_change.call hsh
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end
424
+ end
425
+ end
426
+
427
+ class ArrayInput < React::Component::Base
428
+ param :value
429
+ param :on_change
430
+
431
+ before_mount do
432
+ state.v! ''
433
+ end
434
+
435
+ def render
436
+ span do
437
+ input(value: state.v).on(:change) do |event|
438
+ state.v! event.target.value
439
+ end.on(:keyDown) do |event|
440
+ if event.key_code == 13
441
+ list = params.value.dup
442
+ list << event.target.value
443
+ params.on_change.call list
444
+ state.v! ''
445
+ end
446
+ end
447
+ table do
448
+ tr do
449
+ th{' '}
450
+ th{' '}
451
+ end
452
+ params.value.each do |v|
453
+ tr do
454
+ td{v}
455
+ td{i(class: 'fa fa-times')}.on(:click) do
456
+ list = params.value.dup
457
+ list.delete v
458
+ params.on_change.call list
459
+ end
460
+ end
461
+ end
462
+ end
463
+ end
464
+ end
465
+ end
466
+
467
+ class SelectInput < React::Component::Base
468
+ param :on_change
469
+ param :value
470
+ param :options
471
+ param :dirty
472
+
473
+ include ClassesInput
474
+
475
+ def render
476
+ span do
477
+ select(class: 'select ' + dirty_class, value: params.value) do
478
+ option{''}
479
+ params.options.each {|val| option(value: val){val}}
480
+ end.on(:change) {|event| params.on_change.call event.target.value}
481
+ end
482
+ end
483
+ end
484
+
485
+ class SelectObjectInput < React::Component::Base
486
+ param :on_change
487
+ param :value
488
+ param :options
489
+ param :dirty
490
+ param :display
491
+
492
+ include ClassesInput
493
+
494
+ before_mount do
495
+ @map = {}
496
+ end
497
+
498
+ def render
499
+ @map = {}
500
+ params.options.each {|val| @map[val[params.display]] = val}
501
+ span do
502
+ select(class: 'select ' + dirty_class, value: params.value) do
503
+ option{''}
504
+ params.options.each {|val| option(value: val[params.display]){val[params.display]}}
505
+ end.on(:change) {|event| params.on_change.call @map[event.target.value]}
506
+ end
507
+ end
508
+ end
509
+
510
+ class MultipleSelectInput < React::Component::Base
511
+ param :on_change
512
+ param :options
513
+ param :value
514
+
515
+ def render
516
+ span do
517
+ select(class: 'select ', multiple: true, value: params.value) do
518
+ option{''}
519
+ params.options.each {|val| option(value: val){val}} #(selected: params.values.include? val){val}}
520
+ end.on(:change) do |event|
521
+ list = params.value.dup
522
+ if list.include? event.target.value
523
+ list.delete event.target.value
524
+ else
525
+ list << event.target.value
526
+ end
527
+ params.on_change.call list
528
+ end
529
+ =begin
530
+ table do
531
+ tr do
532
+ th{' '}
533
+ th{' '}
534
+ end
535
+ params.values.each do |v|
536
+ tr do
537
+ td{v}
538
+ td{i(class: 'fa fa-times fa-2x')}.on(:click) do
539
+ list = params.values.dup
540
+ list.delete v
541
+ params.on_change.call list
542
+ end
543
+ end
544
+ end
545
+ end
546
+ =end
547
+ end
548
+ end
549
+ end
550
+
551
+ class FormButtons < React::Component::Base
552
+ param :valid
553
+ param :dirty
554
+ param :save
555
+ param :discard
556
+
557
+ before_mount do
558
+ state.discard! false
559
+ end
560
+
561
+ def render
562
+ div do
563
+ i(class: 'save fa fa-floppy-o fa-2x').on(:click){params.save.call} if params.valid && params.dirty
564
+ i(class: 'discard fa fa-times fa-2x').on(:click) do
565
+ if params.dirty
566
+ state.discard! true
567
+ else
568
+ params.discard.call
569
+ end
570
+ end if !state.discard
571
+ i(class: 'rdiscard fa fa-times fa-4x').on(:click) {params.discard.call; state.discard! false} if state.discard
572
+ end
573
+ end
574
+ end
575
+
576
+ class Form < React::Component::Base
577
+
578
+ include MNotification
579
+
580
+ before_mount do
581
+ @dirty = Set.new
582
+ @refs = {}
583
+ state.discard! false
584
+ state.dirty! false
585
+ end
586
+
587
+ before_unmount do
588
+ @rvs.each_pair {|k, v| v.remove k; v.remove_form self} if @rvs
589
+ end
590
+
591
+ def dirty?
592
+ !@dirty.empty?
593
+ end
594
+
595
+ def expand_attr(hsh={})
596
+ lambda do |value|
597
+ value.each_pair do |k, v|
598
+ if !hsh[k].nil?
599
+ k = hsh[k]
600
+ end
601
+ @dirty.add k
602
+ state.__send__(k+'!', v)
603
+ state.__send__('dirty_' + k + '!', true)
604
+ state.dirty! true
605
+ end
606
+ end
607
+ end
608
+
609
+ def change_attr(attr)
610
+ lambda do |value|
611
+ @dirty.add attr
612
+ doc = state.__send__(attr.split('.')[0])
613
+ set_nested(attr, value, doc){|r, v| state.__send__(r+'!', v)}
614
+ state.__send__('dirty_' + attr.gsub('.', '_') + '!', true)
615
+ state.dirty! true
616
+ end
617
+ end
618
+
619
+ def hash_from_state
620
+ ret = {}
621
+ @dirty.each do |attr|
622
+ get_nested!(ret, attr) {|r| state.__send__(r)}
623
+ end
624
+ @@constants.each do |cte|
625
+ get_nested!(ret, cte) {|r| params.__send__(r)}
626
+ end if @@constants
627
+ ret
628
+ end
629
+
630
+ def save
631
+ if state.id
632
+ update
633
+ else
634
+ insert
635
+ end
636
+ state.discard! false
637
+ state.dirty! false
638
+ end
639
+
640
+ def clear_dirty
641
+ @dirty.each {|attr| state.__send__('dirty_' + attr.gsub('.', '_')+'!', false)}
642
+ @dirty.clear
643
+ end
644
+
645
+ def discard
646
+ @selected.value = nil
647
+ clear
648
+ #@dirty.each {|attr| state.__send__('dirty_' + attr+'!', false)}
649
+ #@dirty.clear
650
+ clear_dirty
651
+ state.discard! false
652
+ state.dirty! false
653
+ end
654
+
655
+ def insert
656
+ $controller.insert(@@table, hash_from_state).then do |response|
657
+ if response.nil?
658
+ #$notifications.add ['error', 'form: data not inserted', 1] if $notifications
659
+ notify_error 'form: data not inserted', 1
660
+ else
661
+ params.selected.value = response
662
+ #$notifications.add ['ok', 'form: data inserted', 1] if $notifications
663
+ notify_ok 'form: data inserted', 1
664
+ end
665
+ end
666
+ #@dirty.each {|attr| state.__send__('dirty_' + attr+'!', false)}
667
+ #@dirty.clear
668
+ clear_dirty
669
+ end
670
+
671
+ def update
672
+ $controller.update(@@table, state.id, hash_from_state).then do |count|
673
+ if count == 0
674
+ #$notifications.add ['error', 'form: data not updated', 1] if $notifications
675
+ notify_error 'form: data not updated', 1
676
+ elsif count == 1
677
+ #$notifications.add ['ok', 'form: data updated', 1] if $notifications
678
+ notify_ok 'form: data updated', 1
679
+ end
680
+ end
681
+ #@dirty.each {|attr| state.__send__('dirty_' + attr.gsub('.', '_')+'!', false)}
682
+ #@dirty.clear
683
+ clear_dirty
684
+ end
685
+
686
+ def get selected
687
+ @selected = selected
688
+ selected.add_form self
689
+ @rvs = reactive(selected) do
690
+ @dirty.each {|attr| state.__send__('dirty_' + attr+'!', false)}
691
+ @dirty.clear
692
+ clear
693
+ $controller.rpc('get_' + @@table, selected.value).then do|response|
694
+ response.each do |k, v|
695
+ state.__send__(k+'!', v)
696
+ end
697
+ end
698
+ end
699
+ end
700
+
701
+ def get_unique kw
702
+ k, selected = kw.shift
703
+ @selected = selected
704
+ selected.add_form self
705
+ @rvs = reactive(selected) do
706
+ @dirty.each {|attr| state.__send__('dirty_' + attr+'!', false)}
707
+ @dirty.clear
708
+ clear
709
+ $controller.rpc('get_unique_' + @@table, k, selected.value).then do|response|
710
+ response.each do |k, v|
711
+ state.__send__(k+'!', v)
712
+ end
713
+ end
714
+ end
715
+ end
716
+ end
717
+
718
+ module Modal
719
+ def render
720
+ div(class: 'modal') do
721
+ div(class: 'modal-center') do
722
+ content
723
+ end
724
+ end
725
+ end
726
+ end
727
+
728
+ module AbstractPopover
729
+ def render
730
+ klass = 'inactive animated fadeOut'
731
+ if params.show == 'visible'
732
+ klass = 'active animated fadeIn'
733
+
734
+ h = $document[params.target].height
735
+ x = $document[params.target].position.x
736
+ y = $document[params.target].position.y
737
+
738
+ $document[params.id].offset.x = x
739
+ $document[params.id].offset.y = y+h
740
+ $window.after(5){params.close.call}
741
+ end
742
+ div(id: params.id) do
743
+ div(class: 'popover ' + klass) do
744
+ div(class: 'arrow-up')
745
+ div(class: 'box') do
746
+ div(class: 'close'){i(class: 'fa fa-times')}.on(:click){params.close.call}
747
+ div(class: 'content'){content}
748
+ end
749
+ end
750
+ end
751
+ end
752
+ end
753
+
754
+ #example
755
+ =begin
756
+ class Popover < React::Component::Base
757
+ param :show
758
+ param :close
759
+ param :id
760
+ param :target_id
761
+ include AbstractPopover
762
+
763
+ def content
764
+ div do
765
+ b{'hello'}
766
+ div{' there'}
767
+ end
768
+ end
769
+ end
770
+
771
+ Popover(id:'popover', target_id: 'my_input', show: state.show_popup, close: lambda{state.show_popup! 'hidden'})
772
+ #where show_popup can be 'visible' or 'hidden'
773
+ =end
774
+
775
+ class HorizontalMenu < React::Component::Base
776
+ param :options
777
+ param :set_page
778
+ param :page
779
+ param :language
780
+
781
+ def active page
782
+ params.page == page ? 'active': ''
783
+ end
784
+
785
+ def render
786
+ div(class: 'no-print') do
787
+ ul(class: 'menu') do
788
+ params.options.each_pair do |k, v|
789
+ li(class: 'menu-item ' + active(k)){a(href: '#'){v}.on(:click){params.set_page.call k}}
790
+ end
791
+ li{a(href: '#'){'en'}.on(:click){params.language.value = 'en'}}
792
+ li{a(href: '#'){'es'}.on(:click){params.language.value = 'es'}}
793
+ end if params.options.length > 1
794
+ end
795
+ end
796
+ end