omf_web 1.2.1 → 1.2.2

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.
data/.gitignore CHANGED
@@ -20,6 +20,7 @@ Gemfile.lock
20
20
  share/htdocs/vendor/valums-file-uploader-cf7bfb1/.gitignore
21
21
  share/htdocs/vendor/valums-file-uploader-cf7bfb1/.gitignore
22
22
  .project
23
+ .settings
23
24
 
24
25
  UNUSED/
25
26
  *.swp
@@ -27,3 +28,4 @@ UNUSED/
27
28
 
28
29
  share/htdocs/vendor/jquery-ui-1.8.23/development-bundle
29
30
  example/wimax
31
+ vendor
@@ -0,0 +1,12 @@
1
+ server:
2
+ page_title: 'Tutorial 1'
3
+ port: 4030
4
+
5
+ tabs:
6
+ - name: Main
7
+ priority: 300
8
+ widgets:
9
+ - name: Description
10
+ type: text
11
+ content:
12
+ text: Hello World!
@@ -0,0 +1 @@
1
+ ts:double,generator:string,voltage:double,noise:double
@@ -0,0 +1,28 @@
1
+ server:
2
+ page_title: 'Tutorial 2'
3
+ port: 4030
4
+
5
+ data_sources:
6
+ - id: voltage
7
+ file: gen.csv
8
+
9
+ tabs:
10
+ - name: Main
11
+ priority: 300
12
+ widgets:
13
+ - name: Generator
14
+ type: data/line_chart3
15
+ data_source:
16
+ name: voltage
17
+ mapping:
18
+ x_axis: ts
19
+ y_axis: voltage
20
+ group_by: generator
21
+ axis:
22
+ x:
23
+ legend: Time [sec]
24
+ y:
25
+ legend: Voltage [V]
26
+ ticks:
27
+ format: ".2s"
28
+
@@ -0,0 +1,37 @@
1
+ server:
2
+ page_title: Tutorial 3 - Stacked Widgets
3
+ port: 4030
4
+
5
+ data_sources:
6
+ - id: voltage
7
+ file: ../tut02/gen.csv
8
+
9
+ tabs:
10
+ - name: Main
11
+ priority: 300
12
+ widgets:
13
+ - type: layout/stacked
14
+ info: This widget shows the voltage reported for varous generators
15
+ widgets:
16
+ - name: Generator
17
+ type: data/line_chart3
18
+ data_source:
19
+ name: voltage
20
+ mapping:
21
+ x_axis: ts
22
+ y_axis: voltage
23
+ group_by: generator
24
+ axis:
25
+ x:
26
+ legend: Time [sec]
27
+ y:
28
+ legend: Voltage [V]
29
+ ticks:
30
+ format: ".2s"
31
+
32
+ - name: Table
33
+ type: data/table2
34
+ data_source:
35
+ name: voltage
36
+
37
+
@@ -0,0 +1,56 @@
1
+ require 'sequel'
2
+
3
+ File.unlink('gen.sq3') if File.readable?('gen.sq3')
4
+ db = Sequel.connect 'sqlite://gen.sq3'
5
+
6
+ db.create_table '_senders'.to_sym do
7
+ Integer :id
8
+ String :name
9
+ end
10
+ db['_senders'.to_sym].insert(id: 1, name: 'test')
11
+
12
+ db.create_table :voltage do
13
+ Integer :oml_sender_id
14
+ Integer :oml_seq
15
+ Float :oml_ts_client
16
+ Float :oml_ts_server
17
+ String :generator
18
+ Float :voltage
19
+ Float :noise
20
+ end
21
+ $out_t = db[:voltage]
22
+ $seq = 0
23
+
24
+
25
+ $start = Time.now.to_i
26
+ def measure(gen, voltage, noise)
27
+ now = Time.now.to_f - $start
28
+ r = {
29
+ oml_sender_id: 1, oml_seq: $seq,
30
+ oml_ts_client: now, oml_ts_server: now,
31
+ generator: gen,
32
+ voltage: voltage,
33
+ noise: noise
34
+ }
35
+ $out_t.insert(r)
36
+ $seq += 1
37
+ end
38
+
39
+ def r; rand - 0.5; end
40
+ def noise(mul); return 4 * mul * r() * r(); end
41
+
42
+ ang = 0;
43
+ step = Math::PI * 15 / 180;
44
+ loop do
45
+ n1 = noise(0.02);
46
+ a1 = Math.sin(ang);
47
+ measure('gen1', a1 + n1, n1);
48
+ n2 = noise(0.01);
49
+ a2 = Math.cos(ang);
50
+ measure('gen2', a2 + n2, n2);
51
+ ang += step;
52
+
53
+ sleep 0.2
54
+ end
55
+
56
+
Binary file
@@ -0,0 +1,47 @@
1
+ server:
2
+ page_title: Tutorial 4 - Realtime Graph
3
+ port: 4030
4
+
5
+ data_sources:
6
+ - id: voltage
7
+ table: voltage
8
+ check_interval: 2
9
+ max_size: 100
10
+ database:
11
+ id: gen
12
+ url: sqlite:gen.sq3
13
+
14
+ tabs:
15
+ - name: Main
16
+ priority: 300
17
+ widgets:
18
+ - name: Voltage
19
+ type: data/line_chart3
20
+ data_source:
21
+ name: voltage
22
+ mapping:
23
+ x_axis: oml_ts_client
24
+ y_axis: voltage
25
+ group_by: generator
26
+ axis:
27
+ x:
28
+ legend: Time [sec]
29
+ y:
30
+ legend: Voltage [V]
31
+
32
+ - name: Noise
33
+ type: data/line_chart3
34
+ data_source:
35
+ name: voltage
36
+ mapping:
37
+ x_axis: oml_ts_client
38
+ y_axis: noise
39
+ group_by: generator
40
+ axis:
41
+ x:
42
+ legend: Time [sec]
43
+ y:
44
+ legend: Noise [V]
45
+ ticks:
46
+ format: ".2s"
47
+
@@ -0,0 +1,125 @@
1
+ protocol: 4
2
+ content: text
3
+ domain: foo
4
+ start-time: 1387441246
5
+ sender-id: maxs-laptop-3.local-35511
6
+ app-name: gen
7
+ schema: 0 _experiment_metadata subject:string key:string value:string
8
+ schema: 1 gen_voltage generator:string voltage:double noise:double
9
+
10
+ 0.000541 1 1 gen1 0.009052262991847197 0.009052262991847197
11
+ 0.000664 1 2 gen2 1.000238176848893 0.00023817684889296177
12
+ 0.201854 1 3 gen1 0.258828739904194 9.69480167331446e-06
13
+ 0.202005 1 4 gen2 0.9688224880955606 0.0028966618064922662
14
+ 0.403129 1 5 gen1 0.49822187353019903 -0.001778126469800914
15
+ 0.403243 1 6 gen2 0.8685805711612515 0.0025551673768128393
16
+ 0.604439 1 7 gen1 0.7018571673754107 -0.005249613811136794
17
+ 0.604592 1 8 gen2 0.6990906726275941 -0.008016108558953511
18
+ 0.805799 1 9 gen1 0.8686216754782153 0.002596271693776792
19
+ 0.805913 1 10 gen2 0.5067729259799549 0.006772925979954824
20
+ 1.006974 1 11 gen1 0.9766814852388673 0.01075565894979911
21
+ 1.007052 1 12 gen2 0.2554201612589873 -0.0033988838435336293
22
+ 1.208202 1 13 gen1 0.9881295616745925 -0.011870438325407514
23
+ 1.208353 1 14 gen2 0.004175973494518424 0.00417597349451814
24
+ 1.409569 1 15 gen1 0.9636146565567362 -0.0023111697323322067
25
+ 1.409766 1 16 gen2 -0.2583664043495907 0.000452640752929745
26
+ 1.611001 1 17 gen1 0.8699910119123253 0.003965608127886516
27
+ 1.611267 1 18 gen2 -0.4963357508476029 0.0036642491523969038
28
+ 1.812464 1 19 gen1 0.707125179793902 1.8398607354376904e-05
29
+ 1.812626 1 20 gen2 -0.7117504151926574 -0.004643634006109943
30
+ 2.013795 1 21 gen1 0.49782332897361886 -0.002176671026381096
31
+ 2.013949 1 22 gen2 -0.8671805905869713 -0.0011551868025326008
32
+ 2.214493 1 23 gen1 0.2759681841041199 0.017149139001599305
33
+ 2.214763 1 24 gen2 -0.9659284181391053 -2.591850037057478e-06
34
+ 2.415727 1 25 gen1 0.006502606057286958 0.00650260605728728
35
+ 2.41585 1 26 gen2 -0.9989695805772422 0.0010304194227578425
36
+ 2.616185 1 27 gen1 -0.2474792019788636 0.011339843123657626
37
+ 2.616457 1 28 gen2 -0.9665593639002044 -0.0006335376111361135
38
+ 2.817713 1 29 gen1 -0.500986141433576 -0.000986141433575397
39
+ 2.817904 1 30 gen2 -0.8640558969794142 0.0019695068050241704
40
+ 3.018555 1 31 gen1 -0.709341248186044 -0.002234466999496018
41
+ 3.018822 1 32 gen2 -0.703823503254065 0.0032832779324820107
42
+ 3.220049 1 33 gen1 -0.8540833586659958 0.011942045118443004
43
+ 3.220201 1 34 gen2 -0.491698229883797 0.008301770116202652
44
+ 3.421419 1 35 gen1 -0.978054979433912 -0.01212915314484368
45
+ 3.421512 1 36 gen2 -0.26191057760657466 -0.003091532504054028
46
+ 3.622692 1 37 gen1 -1.0041761696946705 -0.004176169694670427
47
+ 3.622888 1 38 gen2 0.000266109456927552 0.0002661094569277357
48
+ 3.824131 1 39 gen1 -0.9660244364327265 -9.861014365809152e-05
49
+ 3.82423 1 40 gen2 0.2655018642868284 0.006682819184308119
50
+ 4.024601 1 41 gen1 -0.8672992538615825 -0.001273850077143368
51
+ 4.024755 1 42 gen2 0.4965828131825105 -0.0034171868174887833
52
+ 4.225872 1 43 gen1 -0.7145364332624615 -0.007429652075913183
53
+ 4.226061 1 44 gen2 0.7094198260620043 0.0023130448754576072
54
+ 4.427303 1 45 gen1 -0.49553047196513217 0.004469528034869077
55
+ 4.427431 1 46 gen2 0.8664569342400065 0.0004315304555685775
56
+ 4.628538 1 47 gen1 -0.25868741744082724 0.0001316276616951693
57
+ 4.628651 1 48 gen2 0.9695495593984652 0.0036237331093973346
58
+ 4.829546 1 49 gen1 -0.0056269230441516015 -0.0056269230441495805
59
+ 4.829678 1 50 gen2 1.000286677159241 0.0002866771592408587
60
+ 5.030135 1 51 gen1 0.26221854668419375 0.0033995015816752445
61
+ 5.030333 1 52 gen2 0.9633585409841711 -0.0025672853048978326
62
+ 5.231567 1 53 gen1 0.49777042942797545 -0.0022295705720222583
63
+ 5.231723 1 54 gen2 0.8667578290164825 0.0007324252320425557
64
+ 5.432044 1 55 gen1 0.7099132050047163 0.002806423818170901
65
+ 5.432202 1 56 gen2 0.709795708621591 0.0026889274350414567
66
+ 5.633394 1 57 gen1 0.8765279299793987 0.010502526194961571
67
+ 5.633502 1 58 gen2 0.5024812578225554 0.0024812578225524903
68
+ 5.834711 1 59 gen1 0.9576089552031831 -0.008316871085884175
69
+ 5.834844 1 60 gen2 0.2632904070984241 0.004471361995899884
70
+ 6.035931 1 61 gen1 1.0036072780529801 0.0036072780529801925
71
+ 6.036029 1 62 gen2 -0.002945853864879225 -0.002945853864883084
72
+ 6.236382 1 63 gen1 0.9793047816231178 0.013378955334048552
73
+ 6.236551 1 64 gen2 -0.2535797763911805 0.005239268711336159
74
+ 6.437722 1 65 gen1 0.867501846509946 0.001476442725505107
75
+ 6.437851 1 66 gen2 -0.5016559171277346 -0.00165591712773846
76
+ 6.639042 1 67 gen1 0.7105133062669828 0.003406525080431909
77
+ 6.639149 1 68 gen2 -0.7118657833841097 -0.004759002197565484
78
+ 6.840287 1 69 gen1 0.5010374979712995 0.0010374979712951564
79
+ 6.840395 1 70 gen2 -0.8631536228642781 0.0028717809201580504
80
+ 7.041641 1 71 gen1 0.26104366950719377 0.0022246244046677344
81
+ 7.041746 1 72 gen2 -0.9673391716909063 -0.0014133454018395162
82
+ 7.242609 1 73 gen1 0.013138412276303276 0.013138412276297579
83
+ 7.242768 1 74 gen2 -0.996419808970396 0.0035801910296040015
84
+ 7.443049 1 75 gen1 -0.2594940717844798 -0.0006750266819648325
85
+ 7.443209 1 76 gen2 -0.9655017940568454 0.0004240322322244331
86
+ 7.643754 1 77 gen1 -0.5017280961989892 -0.0017280961989946213
87
+ 7.643855 1 78 gen2 -0.8666439757350138 -0.0006185719505720662
88
+ 7.845024 1 79 gen1 -0.710833044153964 -0.0037262629674211134
89
+ 7.845185 1 80 gen2 -0.712118404232411 -0.005011623045858907
90
+ 8.04634 1 81 gen1 -0.8639890749334213 0.0020363288510139134
91
+ 8.046444 1 82 gen2 -0.4981155461038007 0.0018844538962052737
92
+ 8.247403 1 83 gen1 -0.9724175093298063 -0.006491683040739884
93
+ 8.247521 1 84 gen2 -0.2629917637412665 -0.004172718638738747
94
+ 8.448396 1 85 gen1 -0.9972626574173751 0.002737342582624884
95
+ 8.448503 1 86 gen2 -0.006205879343863204 -0.00620587934385567
96
+ 8.649184 1 87 gen1 -0.969092076529548 -0.003166250240477721
97
+ 8.649359 1 88 gen2 0.26237290188814333 0.003553856785630157
98
+ 8.850467 1 89 gen1 -0.8678365955717785 -0.0018111917873358183
99
+ 8.850546 1 90 gen2 0.49936766720269943 -0.0006323327972934984
100
+ 9.05164 1 91 gen1 -0.6947241904470202 0.012382590739533293
101
+ 9.051749 1 92 gen2 0.7043420965663202 -0.0027646846202214366
102
+ 9.252116 1 93 gen1 -0.5000611405373083 -6.114053730073697e-05
103
+ 9.252231 1 94 gen2 0.8650205513420002 -0.0010048524424341925
104
+ 9.452679 1 95 gen1 -0.2611463705367044 -0.0023273254341748947
105
+ 9.452769 1 96 gen2 0.9714777673088831 0.005551941019817127
106
+ 9.653985 1 97 gen1 0.0009345045086265564 0.000934504508635928
107
+ 9.654093 1 98 gen2 1.0019024678600363 0.001902467860036351
108
+ 9.854806 1 99 gen1 0.2595686111414282 0.0007495660389167818
109
+ 9.854918 1 100 gen2 0.9658598738139401 -6.595247513067116e-05
110
+ 10.056092 1 101 gen1 0.4957794151569727 -0.004220584843018661
111
+ 10.056316 1 102 gen2 0.8639320769128302 -0.002093326871613348
112
+ 10.2573 1 103 gen1 0.7099559913341272 0.0028492101475869757
113
+ 10.257393 1 104 gen2 0.7007690522692515 -0.006337728917303325
114
+ 10.45836 1 105 gen1 0.8665676027571488 0.0005421989727154917
115
+ 10.458579 1 106 gen2 0.5003270197892833 0.00032701978927404625
116
+ 10.659818 1 107 gen1 0.9615923308294252 -0.004333495459640287
117
+ 10.659953 1 108 gen2 0.2579735203771516 -0.0008455247253796604
118
+ 10.860999 1 109 gen1 1.0000405508627024 4.05508627023924e-05
119
+ 10.861083 1 110 gen2 -0.000981461777728295 -0.0009814617777395042
120
+ 11.062217 1 111 gen1 0.9579223304488469 -0.008003495840224447
121
+ 11.062359 1 112 gen2 -0.25996047914817483 -0.0011414340456651958
122
+ 11.263536 1 113 gen1 0.8648213953446445 -0.0012040084398000131
123
+ 11.263635 1 114 gen2 -0.4991642283075432 0.0008357716924465582
124
+ 11.464841 1 115 gen1 0.7030706949442987 -0.004036086242257328
125
+ 11.464939 1 116 gen2 -0.7052689708758774 0.0018378103106615525
@@ -0,0 +1,36 @@
1
+ require 'oml4r'
2
+
3
+
4
+ class GenMP < OML4R::MPBase
5
+ name :voltage
6
+
7
+ param :generator
8
+ param :voltage, :type => :double
9
+ param :noise, :type => :double
10
+ end
11
+
12
+ opts = {
13
+ :appName => 'gen',
14
+ :domain => 'foo',
15
+ :collect => 'tcp:localhost:4003'
16
+ }
17
+ OML4R::init(ARGV, opts)
18
+
19
+ def r; rand - 0.5; end
20
+ def noise(mul); return 4 * mul * r() * r(); end
21
+
22
+ ang = 0;
23
+ step = Math::PI * 15 / 180;
24
+ loop do
25
+ n1 = noise(0.02);
26
+ a1 = Math.sin(ang);
27
+ GenMP.inject('gen1', a1 + n1, n1);
28
+ n2 = noise(0.01);
29
+ a2 = Math.cos(ang);
30
+ GenMP.inject('gen2', a2 + n2, n2);
31
+ ang += step;
32
+
33
+ sleep 0.2
34
+ end
35
+
36
+
@@ -0,0 +1,47 @@
1
+ server:
2
+ page_title: Tutorial 5 - OML Direct Graphs
3
+ port: 4030
4
+
5
+ data_sources:
6
+ - id: voltage
7
+ max_size: 100
8
+ omsp:
9
+ port: 4003
10
+ stream_name: gen_voltage
11
+
12
+ tabs:
13
+ - name: Main
14
+ priority: 300
15
+ widgets:
16
+ - name: Voltage
17
+ type: data/line_chart3
18
+ data_source:
19
+ name: voltage
20
+ mapping:
21
+ x_axis: oml_ts
22
+ y_axis: voltage
23
+ group_by: generator
24
+ axis:
25
+ x:
26
+ legend: Time [sec]
27
+ y:
28
+ legend: Voltage [V]
29
+
30
+ - name: Noise
31
+ type: data/histogram2
32
+ info: This widget shows the distribution of the noise on the generator output.
33
+ density: true
34
+ data_source:
35
+ name: voltage
36
+ mapping:
37
+ value: noise
38
+ group_by: generator
39
+ axis:
40
+ x:
41
+ legend: Noise (V)
42
+ ticks:
43
+ format: ".2s"
44
+ y:
45
+ legend: Density
46
+
47
+
@@ -135,9 +135,9 @@ module OMF::Web
135
135
  res = []
136
136
  fs = _find_files(search_pattern, tree, nil, res)
137
137
 
138
- if (mt = opts[:mime_type])
139
- fs = fs.select { |f| f[:mime_type] == mt }
140
- end
138
+ # if (mt = opts[:mime_type])
139
+ # fs = fs.select { |f| File.fnmatch(mt, f[:mime_type]) }
140
+ # end
141
141
  fs
142
142
  end
143
143
 
@@ -116,7 +116,8 @@ module OMF::Web
116
116
  path = e.path
117
117
  if path.match(search_pattern)
118
118
  mt = mime_type_for_file(path)
119
- next if mime_type != nil && mime_type != mt
119
+ # subselect mime type in class method
120
+ #next if mime_type != nil && !File.fnmatch(mime_type, mt)
120
121
  res << {:url => get_url_for_path(path), :path => path, #:name => 'foo',
121
122
  :mime_type => mt}
122
123
  end
@@ -128,7 +128,12 @@ module OMF::Web
128
128
  fsa = (opts[:repo_iterator] || [@@primary_repository]).map do |repo|
129
129
  repo.find_files(selector, opts)
130
130
  end
131
+
131
132
  fs = fsa.flatten
133
+ if (mt = opts[:mime_type])
134
+ fs = fs.select { |f| File.fnmatch(mt, f[:mime_type]) }
135
+ end
136
+
132
137
  if (max = opts[:max])
133
138
  fs = fs[0, max]
134
139
  end
@@ -18,7 +18,7 @@ module OMF::Web
18
18
  # @returns url for repo
19
19
  #
20
20
  def self.create_from_text(descr, opts)
21
- puts "STATIC>>> #{opts}"
21
+ #puts "STATIC>>> #{opts}"
22
22
  unless (text = descr[:text])
23
23
  text "Missing 'text' declaration in 'content'"
24
24
  end
@@ -46,12 +46,18 @@ module OMF::Web
46
46
  end
47
47
 
48
48
  def self.[](name)
49
+ find(name)
50
+ end
51
+
52
+ def self.find(name, warn_if_not_exist = true)
49
53
  name = name.to_sym
50
54
  unless dsp = OMF::Web::SessionStore[name, :dsp]
51
55
  if ds = @@datasources[name]
52
56
  dsp = OMF::Web::SessionStore[name, :dsp] = self.new(name, ds)
53
57
  else
54
- warn "Requesting unknown datasource '#{name}', only know about '#{@@datasources.keys.join(', ')}'."
58
+ if warn_if_not_exist
59
+ warn "Requesting unknown datasource '#{name}', only know about '#{@@datasources.keys.join(', ')}'."
60
+ end
55
61
  end
56
62
  end
57
63
  dsp
@@ -109,7 +115,7 @@ module OMF::Web
109
115
  return [proxy]
110
116
  end
111
117
 
112
- attr_reader :name
118
+ attr_reader :name, :data_source
113
119
 
114
120
  def reset()
115
121
  # TODO: Figure out partial sending
@@ -147,26 +153,28 @@ module OMF::Web
147
153
  dsp
148
154
  end
149
155
 
150
- def to_javascript(opts = {})
151
- #puts "to_java>>>>> #{opts.inspect}"
156
+ def to_hash(opts = {})
152
157
  sid = Thread.current["sessionID"]
153
158
  opts = opts.dup
154
- opts[:name] = @name
159
+ opts[:name] ||= @name
160
+ opts[:id] = @name
161
+ opts[:stream] = @name # TODO: Should retire. Code should use :id
155
162
  opts[:schema] = @data_source.schema.describe
156
- opts[:update_url] = "/_update/#{@name}?sid=#{sid}"
163
+ opts[:update_url] ||= "/_update/#{@name}?sid=#{sid}"
164
+ opts[:ws_url] ||= "/_ws?sid=#{sid}"
157
165
  opts[:sid] = sid
158
- #opts[:is_static] = (@data_source.respond_to? :static?) ? @data_source.static? : false
159
166
  unless opts[:slice] # don't send any data if this is a sliced one
160
- #opts[:rows] = @data_source.rows[0 .. 20]
161
167
  opts[:rows] = []
162
168
  opts[:offset] = @data_source.offset
163
169
  end
164
- #puts "to_java2>>>>> #{opts.inspect}"
170
+ opts
171
+ end
165
172
 
173
+ def to_javascript(opts = {})
174
+ opts = to_hash(opts)
166
175
  %{
167
176
  ds.register(#{opts.to_json});
168
177
  }
169
-
170
178
  end
171
179
 
172
180
  def initialize(name, data_source)
@@ -48,8 +48,26 @@ module OMF::Web
48
48
  def self.find_across_sessions(&block)
49
49
  @@sessions.values.map { |v| v[:content] }.find(&block)
50
50
  end
51
+
52
+ # Return a session context which will execute block given to
53
+ # #call in this session context
54
+ #
55
+ def self.session_context
56
+ SessionContext.new
57
+ end
51
58
  end # SessionStore
52
59
 
60
+ class SessionContext
61
+ def initialize
62
+ @sid = Thread.current["sessionID"]
63
+ end
64
+
65
+ def call(&block)
66
+ Thread.current["sessionID"] = @sid
67
+ block.call
68
+ end
69
+ end
70
+
53
71
  end # OMF:Web
54
72
 
55
73
 
@@ -46,7 +46,7 @@ module OMF::Web::Theme
46
46
  div :id => base_id, :class => "codemirror_widget" do
47
47
 
48
48
  js_toolbar = []
49
- div :class => "codemirror_toolbar_container widget-toolbar" do
49
+ div :class => "codemirror_toolbar_container widget_toolbar" do
50
50
  ol :class => "codemirror_toolbar" do
51
51
  ['save', 'undo', 'redo'].each do |name|
52
52
  id = "#{base_id}_#{name}_a"