forma 0.0.0 → 0.1.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.
data/lib/forma/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module Forma
3
- VERSION = "0.0.0"
3
+ VERSION = "0.1.0"
4
4
  end
data/lib/forma.rb CHANGED
@@ -1,4 +1,12 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  require 'active_support/core_ext'
3
3
  require 'forma/version'
4
+ require 'forma/utils'
4
5
  require 'forma/html'
6
+ require 'forma/config'
7
+ require 'forma/helpers'
8
+ require 'forma/action'
9
+ require 'forma/form'
10
+ require 'forma/table'
11
+ require 'forma/field'
12
+ require 'forma/railtie' if defined?(Rails)
@@ -0,0 +1,20 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe 'Number configuration' do
5
+ specify { Forma.config.num.delimiter.should == ',' }
6
+ specify { Forma.config.num.separator.should == '.' }
7
+ end
8
+
9
+ describe 'Date configuration' do
10
+ specify { Forma.config.date.formatter.should == '%d-%b-%Y' }
11
+ end
12
+
13
+ describe 'Texts configuration' do
14
+ specify { Forma.config.texts.empty.should == '(empty)' }
15
+ end
16
+
17
+ describe 'Map configuration' do
18
+ specify { Forma.config.map.default_latitude == 41.711447 }
19
+ specify { Forma.config.map.default_longitude == 44.754514 }
20
+ end
@@ -0,0 +1,3 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+ include Forma
data/spec/form_spec.rb ADDED
@@ -0,0 +1,33 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ include Forma
5
+
6
+ # describe 'simple form' do
7
+ # before(:all) do
8
+ # @form = Form.new(
9
+ # id: 'user-login',
10
+ # title: 'User Login',
11
+ # collapsible: true,
12
+ # collapsed: false,
13
+ # icon: '/assets/user.png',
14
+ # edit: true,
15
+ # url: '/login',
16
+ # method: 'post',
17
+ # selected_tab: 0,
18
+ # tabs: [
19
+ # Tab.new(title: 'Login', icon: '/assets/user.png', col1: Col.new([
20
+ # TextField.new(name: 'username', label: 'Username', required: true),
21
+ # TextField.new(name: 'password', label: 'Password', required: true)
22
+ # ])),
23
+ # Tab.new(title: 'Reset password', icon: '/assets/password.png'),
24
+ # ]
25
+ # )
26
+ # @element = @form.to_html
27
+ # # puts Nokogiri::XML(@element.to_s, &:noblanks).to_xhtml(indent: 2)
28
+ # end
29
+ # specify { @element.tag.should == 'div' }
30
+ # specify { @element.id.should == 'user-login' }
31
+ # specify { @element.klass.should == [ 'ff-form' ] }
32
+ # # TODO: check other elements
33
+ # end
@@ -0,0 +1,18 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+ include Forma::Helpers
4
+
5
+ # describe "forma helper" do
6
+ # before(:all) do
7
+ # @user_password = { username: 'dimitri', password: 'secret', age: 33, email: 'dimakura@gmail.com' }
8
+ # @forma = forma_for(@user_password) do |f|
9
+ # f.text_field :username
10
+ # f.password_field :password
11
+ # f.number_field :age
12
+ # f.email_field :email
13
+ # end
14
+ # # puts Nokogiri::XML(@forma, &:noblanks).to_xhtml(indent: 2)
15
+ # end
16
+ # specify { @forma.should_not be_nil }
17
+ # specify { @forma.should be_a String }
18
+ # end
data/spec/html_spec.rb ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ include Forma::Html
5
+
6
+ describe 'attribute helper method' do
7
+ specify { attr('id', 'forma4').to_s.should == 'id="forma4"' }
8
+ specify { attr('btn').to_s.should == 'class="btn"' }
9
+ specify { attr(['btn', 'btn-primary']).to_s.should == 'class="btn btn-primary"' }
10
+ specify { attr({'font-size' => '10px', 'font-weight' => 'bold'}).to_s.should == 'style="font-size:10px;font-weight:bold"' }
11
+ end
12
+
13
+ describe 'element creation' do
14
+ specify { el('div').to_s.should == '<div></div>' }
15
+ specify { el('div', attrs: { id: 'forma4' }).to_s.should == '<div id="forma4"></div>' }
16
+ specify { el('div', attrs: { id: 'forma4', class: 'main' }).to_s.should == '<div id="forma4" class="main"></div>' }
17
+ specify { el('div', attrs: { id: 'forma4', class: ['main', 'very-important'], style: {'font-size' => '10px'} }).to_s.should == '<div id="forma4" class="main very-important" style="font-size:10px"></div>' }
18
+ specify { el('div', children: [el('div')]).to_s.should == '<div><div></div></div>' }
19
+ end
20
+
21
+ describe 'element id' do
22
+ specify { el('div').id.should be_nil }
23
+ specify { el('div', attrs: { id: 'forma4' }).id.should == 'forma4' }
24
+ specify { el('div', attrs: { class: 'myform1' }).klass.should == [ 'myform1' ] }
25
+ specify { el('div', attrs: { class: ['myform1', 'myform2'] }).klass.should == [ 'myform1', 'myform2' ] }
26
+ end
data/spec/spec_helper.rb CHANGED
@@ -5,9 +5,3 @@ require 'forma'
5
5
  RSpec.configure do |config|
6
6
  config.include(RSpec::Matchers)
7
7
  end
8
-
9
- def compare_object(obj, properties)
10
- properties.each do |k, v|
11
- specify { obj.send(k).should == v }
12
- end
13
- end
@@ -0,0 +1,63 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class FieldTest < Test::Unit::TestCase
5
+ include Forma
6
+
7
+ def test_general_field
8
+ fld = Field.new(model_name: 'user', name: 'first_name', value: 'fake value', label: 'First Name')
9
+ assert_equal 'user', fld.model_name
10
+ assert_equal 'first_name', fld.name
11
+ assert_equal 'models.user.first_name', fld.localization_key
12
+ assert_equal 'First Name', fld.label
13
+ assert_equal 'First Name', fld.localized_label
14
+ assert_equal nil, fld.hint
15
+ assert_equal '', fld.localized_hint
16
+ assert_equal 'fake value', fld.value
17
+ assert_equal 'user_first_name', fld.id
18
+ end
19
+
20
+ def test_text_field
21
+ model = { first_name: 'Dimitri', last_name: 'Kurashvili' }
22
+ fld_first_name = TextField.new(model_name: 'user', name: 'first_name', model: model, hint: "user's first name")
23
+ fld_last_name = TextField.new(model_name: 'user', name: 'last_name', model: model)
24
+ assert_equal 'first_name', fld_first_name.name
25
+ assert_equal "user's first name", fld_first_name.hint
26
+ assert_equal "user's first name", fld_first_name.localized_hint
27
+ assert_equal 'Dimitri', fld_first_name.value
28
+ assert_equal 'last_name', fld_last_name.name
29
+ assert_equal 'user_first_name', fld_first_name.id
30
+ assert_equal 'user[first_name]', fld_first_name.parameter_name
31
+ assert_equal 'user_last_name', fld_last_name.id
32
+ assert_equal 'user[last_name]', fld_last_name.parameter_name
33
+ fld_first_name_ka = TextField.new(name: 'ka', parent: fld_first_name)
34
+ assert_equal 'user_first_name_ka', fld_first_name_ka.id
35
+ assert_equal 'user[first_name_attributes[ka]]', fld_first_name_ka.parameter_name
36
+ end
37
+
38
+ def test_text_field_value_override
39
+ model = { first_name: 'Dimitri', last_name: 'Kurashvili' }
40
+ fld_first_name = TextField.new(model_name: 'user', name: 'first_name', model: model, value: 'other_name')
41
+ assert_equal 'other_name', fld_first_name.value
42
+ fld_first_name.value = nil
43
+ assert_equal 'Dimitri', fld_first_name.value
44
+ end
45
+
46
+ def test_generated_text_field
47
+ model = { first_name: 'Dimitri', last_name: 'Kurashvili' }
48
+ fld_first_name = TextField.new(model_name: 'user', name: 'first_name', model: model)
49
+ el = fld_first_name.edit_element('Dimitri')
50
+ assert_equal 'input', el.tag
51
+ assert_equal 'user_first_name', el.attrs_by_name('id').first.value
52
+ assert_equal 'user[first_name]', el.attrs_by_name('name').first.value
53
+ assert_equal 'text', el.attrs_by_name('type').first.value
54
+ assert_equal 'Dimitri', el.attrs_by_name('value').first.value
55
+ end
56
+
57
+ def test_compex_field
58
+ model = { first_name: 'Dimitri', last_name: 'Kurashvili' }
59
+ fld = ComplexField.new(fields: [ TextField.new(name: 'first_name'), TextField.new(name: 'last_name') ])
60
+ fld.model = model
61
+ assert_equal ['Dimitri', 'Kurashvili'], fld.value
62
+ end
63
+ end
data/test/form_test.rb ADDED
@@ -0,0 +1,16 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class FormTest < Test::Unit::TestCase
5
+ include Forma
6
+
7
+ def test_general_field
8
+ model = { first_name: 'Dimitri', last_name: 'Kurashvili' }
9
+ fld_first_name = TextField.new(model_name: 'user', name: 'first_name', model: model)
10
+ fld_last_name = TextField.new(model_name: 'user', name: 'last_name', model: model)
11
+ frm = Form.new
12
+ frm.add_field(fld_first_name)
13
+ frm.add_field(fld_last_name)
14
+ # puts frm.to_html
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'test/unit'
3
+ require 'forma'
Binary file
@@ -0,0 +1,147 @@
1
+ (function() {
2
+
3
+ var initilizeCollapsibleElement = function() {
4
+ $('.ff-collapsible').click(function(evt) {
5
+ var activeTitle = $(this);
6
+ var formBody = $(activeTitle.parent().siblings('.ff-collapsible-body')[0]);
7
+ var collapseElement = $(activeTitle.children('.ff-collapse')[0]);
8
+ var isCollapsed = collapseElement.hasClass('ff-collapsed');
9
+ if (isCollapsed) {
10
+ formBody.show();
11
+ collapseElement.removeClass('ff-collapsed');
12
+ } else {
13
+ formBody.hide();
14
+ collapseElement.addClass('ff-collapsed');
15
+ }
16
+ });
17
+ };
18
+
19
+ var initializeTabs = function() {
20
+ $('.ff-tabs-header li').click(function(evnt) {
21
+ var newTab = $(this);
22
+ var oldTab = newTab.siblings('.ff-selected');
23
+ if (oldTab) {
24
+ var newIndex = newTab.parent().children().index(newTab);
25
+ var oldIndex = oldTab.parent().children().index(oldTab);
26
+ var newBody = $($(newTab.parent().parent().children()[1]).children()[newIndex]);
27
+ var oldBody = $($(newTab.parent().parent().children()[1]).children()[oldIndex]);
28
+ oldTab.removeClass("ff-selected");
29
+ oldBody.hide();
30
+ newTab.addClass("ff-selected");
31
+ newBody.show();
32
+ // we need to initialize a map
33
+ // if it was not visible and became
34
+ // visible when the tab was open
35
+ initGoogleMaps();
36
+ }
37
+ });
38
+ };
39
+
40
+ var initializeFormSubmit = function() {
41
+ $('form.ff-wait-on-submit').submit(function(evnt) {
42
+ var btn = $($(this).find('button[type=submit]')[0]);
43
+ btn.html(btn.html() + '...');
44
+ btn.attr('disabled', true);
45
+ return true;
46
+ });
47
+ };
48
+
49
+ var initializeTooltips = function() {
50
+ $('.ff-field-hint').tooltip({ placement: 'right', trigger: 'click' });
51
+ $('.ff-action').tooltip({ placement: 'top' });
52
+ // TODO: any other tooltips?
53
+ };
54
+
55
+ var initializeSelectFields = function() {
56
+ $('.ff-select-link').click(function() {
57
+ var link = $(this);
58
+ var url = link.attr('data-url');
59
+ var height = link.attr('data-height');
60
+ var width = link.attr('data-width');
61
+ var screenLeft = window.screenLeft;
62
+ var screenTop = window.screenTop;
63
+ var browserHeight = window.outerHeight;
64
+ var browserWidth = window.outerWidth;
65
+ var left = parseInt((screenLeft + browserWidth - width) / 2);
66
+ var top = parseInt((screenTop + browserHeight - height) / 2);
67
+ var properties = ['height=', height, ',width=', width, ',left=', left, ',top=', top];
68
+ window.open(url, link.attr('data-id'), properties.join(''));
69
+ });
70
+ };
71
+
72
+ // google map initialization
73
+
74
+ var mapsData = {};
75
+
76
+ var registerGoogleMap = function(id, zoom, center, coordinates, draggable) {
77
+ mapsData[id] = { id: id, zoom: zoom, center: center, coordinates: coordinates, draggable: draggable }
78
+ };
79
+
80
+ var getGoogleMap = function(id) {
81
+ return mapsData[id];
82
+ };
83
+
84
+ var googleMapInitialization = function(data) {
85
+ var id = data['id'];
86
+ if ($('#' + id).is(':visible')) {
87
+ var mapOptions = {
88
+ center: new google.maps.LatLng(data.center.latitude, data.center.longitude),
89
+ zoom: data.zoom,
90
+ mapTypeId: google.maps.MapTypeId.HYBRID //ROADMAP
91
+ };
92
+ var map = new google.maps.Map(document.getElementById(id), mapOptions);
93
+ var markers = [ ];
94
+ for (var i = 0, l = data.coordinates.length; i < l; i++) {
95
+ var markerData = data.coordinates[i];
96
+ var markerPosition = new google.maps.LatLng(markerData.latitude, markerData.longitude);
97
+ var marker = new google.maps.Marker({
98
+ position: markerPosition,
99
+ animation: google.maps.Animation.DROP,
100
+ map: map,
101
+ draggable: data.draggable,
102
+ });
103
+ markers.push(marker);
104
+ google.maps.event.addListener(marker, 'dragend', function() {
105
+ var pos = marker.getPosition();
106
+ $('#' + id + '_latitude').val(pos.lat() + '');
107
+ $('#' + id + '_longitude').val(pos.lng());
108
+ });
109
+ }
110
+ data.map = map;
111
+ data.markers = markers;
112
+ }
113
+ };
114
+
115
+ var initGoogleMaps = function() {
116
+ for (var id in mapsData) {
117
+ var data = mapsData[id];
118
+ if (data && !data.map) {
119
+ googleMapInitialization(data);
120
+ }
121
+ }
122
+ };
123
+
124
+ // prepare function
125
+
126
+ var ready = function() {
127
+ initilizeCollapsibleElement();
128
+ initializeTabs();
129
+ initializeFormSubmit();
130
+ initializeTooltips();
131
+ initGoogleMaps();
132
+ initializeSelectFields();
133
+ };
134
+
135
+ // turbolink initilization!
136
+ // http://railscasts.com/episodes/390-turbolinks?view=asciicast
137
+ $(document).ready(ready);
138
+ $(document).on('page:load', ready);
139
+
140
+ // external API
141
+
142
+ window.forma = {
143
+ registerGoogleMap: registerGoogleMap,
144
+ getGoogleMap: getGoogleMap,
145
+ };
146
+
147
+ })();
@@ -1 +1,41 @@
1
- .ff{font-size:14px;}
1
+ .user-select-none{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;}
2
+ .ff-form,.ff-table{margin-bottom:16px;}.ff-form .ff-action,.ff-table .ff-action{margin-left:4px;}.ff-form .ff-action:first-child,.ff-table .ff-action:first-child{margin-left:0;}
3
+ .ff-form .ff-action img,.ff-table .ff-action img{padding-right:4px;height:16px;width:16px;}
4
+ .ff-form .ff-title,.ff-table .ff-title{position:relative;}.ff-form .ff-title .ff-active-title,.ff-table .ff-title .ff-active-title{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;cursor:pointer;display:inline-block;padding:4px 8px;font-weight:bold;font-size:14px;}.ff-form .ff-title .ff-active-title .ff-collapse,.ff-table .ff-title .ff-active-title .ff-collapse{display:inline-block;height:14px;width:14px;margin-right:4px;margin-top:2px;}
5
+ .ff-form .ff-title .ff-active-title img,.ff-table .ff-title .ff-active-title img{height:16px;width:16px;overflow:hidden;margin-right:4px;}
6
+ .ff-form .ff-title .ff-title-actions,.ff-table .ff-title .ff-title-actions{position:absolute;right:0;top:4px;padding:0 8px;}
7
+ .ff-form .ff-collapse,.ff-table .ff-collapse{background:url(/assets/glyphicons-halflings.png) -313px -119px;}
8
+ .ff-form .ff-collapsed.ff-collapse,.ff-table .ff-collapsed.ff-collapse{background:url(/assets/glyphicons-halflings.png) -456px -72px;}
9
+ .ff-form .ff-field-hint,.ff-table .ff-field-hint{background:url(/assets/glyphicons-halflings.png) -456px -72px;}
10
+ .ff-form .ff-tabs,.ff-table .ff-tabs{padding:4px 0;}.ff-form .ff-tabs ul.ff-tabs-header,.ff-table .ff-tabs ul.ff-tabs-header{list-style-type:none;margin:0;position:relative;border-bottom:1px solid #b1b1b1;}.ff-form .ff-tabs ul.ff-tabs-header li,.ff-table .ff-tabs ul.ff-tabs-header li{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;cursor:pointer;display:inline-block;padding:4px 8px;margin-left:8px;margin-bottom:-1px;border:1px solid transparent;}.ff-form .ff-tabs ul.ff-tabs-header li img,.ff-table .ff-tabs ul.ff-tabs-header li img{margin:0 4px;height:16px;width:16px;}
11
+ .ff-form .ff-tabs ul.ff-tabs-header li.ff-selected,.ff-table .ff-tabs ul.ff-tabs-header li.ff-selected{background:#ffffcc;border:1px solid #b1b1b1;border-bottom:1px solid #ffffcc;}
12
+ .ff-form .ff-tabs .ff-tab-actions,.ff-table .ff-tabs .ff-tab-actions{padding:4px 8px;background:#ffffcc;border:1px solid #b1b1b1;margin-top:-1px;}
13
+ .ff-form .ff-tabs .ff-cols,.ff-table .ff-tabs .ff-cols{margin:4px 0 0 0;position:relative;font-size:13px;}.ff-form .ff-tabs .ff-cols .ff-col-100,.ff-table .ff-tabs .ff-cols .ff-col-100{width:100%;}
14
+ .ff-form .ff-tabs .ff-cols .ff-col-50,.ff-table .ff-tabs .ff-cols .ff-col-50{width:50%;float:left;}
15
+ .ff-form .ff-tabs .ff-cols .ff-col:first-child .ff-col-inner,.ff-table .ff-tabs .ff-cols .ff-col:first-child .ff-col-inner{padding-right:4px;}
16
+ .ff-form .ff-tabs .ff-cols .ff-col:last-child .ff-col-inner,.ff-table .ff-tabs .ff-cols .ff-col:last-child .ff-col-inner{padding-left:4px;}
17
+ .ff-form .ff-tabs .ff-cols .ff-col-inner,.ff-table .ff-tabs .ff-cols .ff-col-inner{padding:0;position:relative;}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field{position:relative;border-bottom:1px solid #b1b1b1;}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field:first-child,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field:first-child{border-top:1px solid #b1b1b1;}
18
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label{display:table-cell;vertical-align:top;position:relative;width:150px;padding:4px 4px 4px 8px;background-color:#c8ffb0;border-right:3px solid transparent;}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label.ff-required,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label.ff-required{border-right:3px solid #cf0000;}
19
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label .ff-field-hint,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label .ff-field-hint{display:block;height:16px;width:16px;position:absolute;right:0;top:8px;background-position:-96px -96px;}
20
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value{display:table-cell;padding:4px;vertical-align:top;}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value .ff-field-actions,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value .ff-field-actions{display:inline-block;border-left:1px solid #b1b1b1;padding-left:8px;margin-left:8px;}
21
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-before,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-before{padding-right:8px;}
22
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-after,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-after{padding-left:8px;}
23
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-complex-part,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-complex-part{display:inline-block;padding-right:8px;}
24
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-field,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-field{display:inline-block;border:none;padding-right:8px;}
25
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-link,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-link{margin-left:8px;}
26
+ .ff-form .ff-tabs .ff-cols:after,.ff-table .ff-tabs .ff-cols:after{content:".";display:block;clear:both;width:0;height:0;overflow:hidden;}
27
+ .ff-form .ff-tabs input,.ff-table .ff-tabs input{padding:4px;margin:0;font-size:13px;}
28
+ .ff-form .ff-tabs select,.ff-table .ff-tabs select{padding:4px;margin:0;font-size:13px;width:auto;}
29
+ .ff-form .ff-tabs .ff-error input,.ff-table .ff-tabs .ff-error input{border:1px solid red;color:red;background:#fff8f8;}
30
+ .ff-form .ff-form-errors,.ff-table .ff-form-errors{margin:8px;padding:8px;border:1px solid red;background:#fee;color:red;}
31
+ .ff-form ul.ff-form-errors li,.ff-table ul.ff-form-errors li{margin-left:16px;}
32
+ .ff-form .ff-field-errors,.ff-table .ff-field-errors{color:red;padding:4px 0;}
33
+ .ff-form .ff-bottom-actions,.ff-table .ff-bottom-actions{padding:8px;border-top:1px solid #b1b1b1;background:#e0e0e0;}
34
+ .ff-form table.ff-common-table,.ff-table table.ff-common-table{margin-top:8px;width:100%;border-collapse:collapse;}.ff-form table.ff-common-table thead,.ff-table table.ff-common-table thead{text-align:left;border-top:1px solid #3316ff;border-bottom:1px solid #3316ff;background:#c8ffb0;}.ff-form table.ff-common-table thead th,.ff-table table.ff-common-table thead th{font-weight:normal;padding:4px 8px;}
35
+ .ff-form table.ff-common-table thead .ff-field-hint,.ff-table table.ff-common-table thead .ff-field-hint{display:inline-block;height:16px;width:16px;background-position:-96px -96px;}
36
+ .ff-form table.ff-common-table tbody td,.ff-table table.ff-common-table tbody td{padding:4px 8px;border-bottom:1px solid #3316ff;}
37
+ .ff-empty{color:#a0a0a0;}
38
+ .ff-table-empty{padding:32px;text-align:center;background-color:#f6f6f6;border:1px solid #a0a0a0;border-top:none;}
39
+ .ff-map{width:100%;height:100%;overflow:hidden;position:absolute;top:0;left:0;right:0;bottom:0;}.ff-map img{max-width:none;}
40
+ .ff-form .ff-title,.ff-table .ff-title{background-color:#cfc8ff;border-bottom:2px solid #3316ff;border-top:2px solid #3316ff;}
41
+ .tooltip-inner{white-space:pre;max-width:none;}