fron-ui 1.0.0rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +38 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/.yardopts +8 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +105 -0
- data/Rakefile +37 -0
- data/Readme.md +4 -0
- data/db.json +192 -0
- data/fron-ui.gemspec +21 -0
- data/lib/fron-ui.rb +1 -0
- data/lib/fron_ui.rb +5 -0
- data/lib/fron_ui/version.rb +7 -0
- data/opal/fron-ui/base.rb +49 -0
- data/opal/fron-ui/behaviors/action.rb +40 -0
- data/opal/fron-ui/behaviors/actions.rb +40 -0
- data/opal/fron-ui/behaviors/confirmation.rb +23 -0
- data/opal/fron-ui/behaviors/dropdown.rb +27 -0
- data/opal/fron-ui/behaviors/file.rb +48 -0
- data/opal/fron-ui/behaviors/intendable_children.rb +76 -0
- data/opal/fron-ui/behaviors/keydown.rb +31 -0
- data/opal/fron-ui/behaviors/loop.rb +41 -0
- data/opal/fron-ui/behaviors/render.rb +30 -0
- data/opal/fron-ui/behaviors/rest.rb +121 -0
- data/opal/fron-ui/behaviors/selectable_children.rb +67 -0
- data/opal/fron-ui/behaviors/serialize.rb +32 -0
- data/opal/fron-ui/behaviors/shortcuts.rb +35 -0
- data/opal/fron-ui/behaviors/state.rb +56 -0
- data/opal/fron-ui/behaviors/transition.rb +63 -0
- data/opal/fron-ui/components/action.rb +18 -0
- data/opal/fron-ui/components/box.rb +17 -0
- data/opal/fron-ui/components/button.rb +61 -0
- data/opal/fron-ui/components/calendar.rb +129 -0
- data/opal/fron-ui/components/checkbox.rb +57 -0
- data/opal/fron-ui/components/chooser.rb +246 -0
- data/opal/fron-ui/components/color_panel.rb +235 -0
- data/opal/fron-ui/components/color_picker.rb +111 -0
- data/opal/fron-ui/components/container.rb +61 -0
- data/opal/fron-ui/components/date_picker.rb +141 -0
- data/opal/fron-ui/components/drag.rb +76 -0
- data/opal/fron-ui/components/dropdown.rb +72 -0
- data/opal/fron-ui/components/icon.rb +29 -0
- data/opal/fron-ui/components/image.rb +77 -0
- data/opal/fron-ui/components/input.rb +30 -0
- data/opal/fron-ui/components/label.rb +9 -0
- data/opal/fron-ui/components/list.rb +34 -0
- data/opal/fron-ui/components/loader.rb +63 -0
- data/opal/fron-ui/components/modal.rb +0 -0
- data/opal/fron-ui/components/notifications.rb +73 -0
- data/opal/fron-ui/components/number.rb +202 -0
- data/opal/fron-ui/components/progress.rb +52 -0
- data/opal/fron-ui/components/slider.rb +47 -0
- data/opal/fron-ui/components/tabs.rb +149 -0
- data/opal/fron-ui/components/textarea.rb +13 -0
- data/opal/fron-ui/components/time.rb +65 -0
- data/opal/fron-ui/components/title.rb +34 -0
- data/opal/fron-ui/examples/blog/index.rb +289 -0
- data/opal/fron-ui/examples/comments/components/comment.rb +75 -0
- data/opal/fron-ui/examples/comments/components/comments.rb +93 -0
- data/opal/fron-ui/examples/comments/components/footer.rb +36 -0
- data/opal/fron-ui/examples/comments/components/header.rb +35 -0
- data/opal/fron-ui/examples/comments/components/list.rb +12 -0
- data/opal/fron-ui/examples/comments/index.rb +6 -0
- data/opal/fron-ui/examples/contacts/components/contacts.rb +100 -0
- data/opal/fron-ui/examples/contacts/components/details.rb +92 -0
- data/opal/fron-ui/examples/contacts/components/item.rb +46 -0
- data/opal/fron-ui/examples/contacts/components/list.rb +10 -0
- data/opal/fron-ui/examples/contacts/components/sidebar.rb +30 -0
- data/opal/fron-ui/examples/contacts/index.rb +6 -0
- data/opal/fron-ui/examples/editor/index.rb +164 -0
- data/opal/fron-ui/examples/kitchensink/index.rb +193 -0
- data/opal/fron-ui/examples/todos/components/item.rb +84 -0
- data/opal/fron-ui/examples/todos/components/options.rb +26 -0
- data/opal/fron-ui/examples/todos/components/todos.rb +145 -0
- data/opal/fron-ui/examples/todos/index.rb +6 -0
- data/opal/fron-ui/examples/webshop/index.rb +0 -0
- data/opal/fron-ui/fonts/ionicons.rb +2954 -0
- data/opal/fron-ui/fonts/open_sans.rb +19 -0
- data/opal/fron-ui/lib/collection.rb +138 -0
- data/opal/fron-ui/lib/date.rb +23 -0
- data/opal/fron-ui/lib/debounce.rb +14 -0
- data/opal/fron-ui/lib/image_loader.rb +13 -0
- data/opal/fron-ui/lib/lorem.rb +93 -0
- data/opal/fron-ui/lib/nil.rb +29 -0
- data/opal/fron-ui/lib/record.rb +23 -0
- data/opal/fron-ui/lib/state_serializer.rb +129 -0
- data/opal/fron-ui/lib/storage.rb +57 -0
- data/opal/fron-ui/spec/setup.rb +40 -0
- data/opal/fron-ui/ui.rb +40 -0
- data/opal/fron-ui/utils/theme_roller.rb +63 -0
- data/opal/fron-ui/vendor/autoprefixer.js +21114 -0
- data/opal/fron-ui/vendor/marked.js +1291 -0
- data/opal/fron-ui/vendor/md5.js +274 -0
- data/opal/fron-ui/vendor/moment.js +3083 -0
- data/opal/fron-ui/vendor/uuid.js +92 -0
- data/opal/fron_ui.rb +13 -0
- data/spec/behaviors/action_spec.rb +34 -0
- data/spec/behaviors/actions_spec.rb +38 -0
- data/spec/behaviors/confirmation_spec.rb +23 -0
- data/spec/behaviors/dropdown_spec.rb +32 -0
- data/spec/behaviors/render_spec.rb +20 -0
- data/spec/behaviors/rest_spec.rb +70 -0
- data/spec/behaviors/selectable_children_spec.rb +40 -0
- data/spec/behaviors/serialize_spec.rb +34 -0
- data/spec/components/action_spec.rb +7 -0
- data/spec/components/base_spec.rb +19 -0
- data/spec/components/box_spec.rb +7 -0
- data/spec/components/button_spec.rb +9 -0
- data/spec/components/calendar_spec.rb +58 -0
- data/spec/components/checkbox_spec.rb +20 -0
- data/spec/components/chooser_spec.rb +75 -0
- data/spec/components/color_panel_spec.rb +49 -0
- data/spec/components/color_picker_spec.rb +41 -0
- data/spec/components/container_spec.rb +23 -0
- data/spec/components/date_picker_spec.rb +71 -0
- data/spec/components/drag_spec.rb +20 -0
- data/spec/components/dropdown_spec.rb +33 -0
- data/spec/components/image_spec.rb +33 -0
- data/spec/components/input_spec.rb +8 -0
- data/spec/components/list_spec.rb +10 -0
- data/spec/components/loader_spec.rb +9 -0
- data/spec/components/notifications_spec.rb +17 -0
- data/spec/components/number_spec.rb +64 -0
- data/spec/components/progress_spec.rb +23 -0
- data/spec/components/slider_spec.rb +25 -0
- data/spec/components/tabs_spec.rb +50 -0
- data/spec/components/textarea_spec.rb +7 -0
- data/spec/components/time_spec.rb +37 -0
- data/spec/components/title_spec.rb +11 -0
- data/spec/examples/comments_spec.rb +72 -0
- data/spec/examples/todos_spec.rb +39 -0
- data/spec/lib/collection_spec.rb +38 -0
- data/spec/lib/lorem_spec.rb +55 -0
- data/spec/lib/state_serializer_spec.rb +58 -0
- data/spec/lib/storage_spec.rb +39 -0
- data/spec/spec_helper.rb +1 -0
- metadata +223 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
module Examples
|
2
|
+
class Comments < UI::Container
|
3
|
+
# Comment
|
4
|
+
class Comment < UI::Container
|
5
|
+
# Includes
|
6
|
+
include UI::Behaviors::Confirmation
|
7
|
+
include UI::Behaviors::Actions
|
8
|
+
include UI::Behaviors::Rest
|
9
|
+
include ::Record
|
10
|
+
|
11
|
+
# REST
|
12
|
+
rest url: 'http://localhost:3000/comments'
|
13
|
+
|
14
|
+
# Tagname
|
15
|
+
tag 'ui-comment'
|
16
|
+
|
17
|
+
# Style
|
18
|
+
style 'ui-comment-body' => { borderBottom: -> { "#{(theme.border_size / 2).em} solid #{colors.background}" },
|
19
|
+
paddingBottom: -> { theme.spacing.em },
|
20
|
+
'p' => { margin: 0,
|
21
|
+
'& + p' => { marginTop: -> { theme.spacing.em } } } },
|
22
|
+
'&:hover ui-comment-header ui-action' => { display: :block }
|
23
|
+
|
24
|
+
# Components
|
25
|
+
component :image, UI::Image, width: 4.em, height: 4.em
|
26
|
+
component :box, UI::Container, flex: 1 do
|
27
|
+
component :header, Header, direction: :row
|
28
|
+
component :body, 'ui-comment-body'
|
29
|
+
component :footer, Footer, direction: :row
|
30
|
+
end
|
31
|
+
|
32
|
+
# Confirmation for destroy
|
33
|
+
confirmation :destroy!, 'Are you sure?'
|
34
|
+
|
35
|
+
# Initializes the comment by setting
|
36
|
+
# the direction attribute
|
37
|
+
def initialize
|
38
|
+
super
|
39
|
+
self[:direction] = :row
|
40
|
+
end
|
41
|
+
|
42
|
+
# Votes up the comment
|
43
|
+
def vote_up
|
44
|
+
update_votes(1)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Votes down the comment
|
48
|
+
def vote_down
|
49
|
+
update_votes(-1)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Updates the vote count by the given number
|
53
|
+
#
|
54
|
+
# @param count [Integer] The count
|
55
|
+
def update_votes(count)
|
56
|
+
update votes: @data[:votes] + count do |data|
|
57
|
+
self.data = data
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Destroys the comment and trigger refresh
|
62
|
+
def destroy!
|
63
|
+
destroy { trigger :refresh }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Renders the comment
|
67
|
+
def render
|
68
|
+
@image.src = @data[:user][:image]
|
69
|
+
@box.body.html = @data[:body]
|
70
|
+
@box.header.render @data
|
71
|
+
@box.footer.render @data
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require_relative 'header'
|
2
|
+
require_relative 'footer'
|
3
|
+
require_relative 'comment'
|
4
|
+
require_relative 'list'
|
5
|
+
|
6
|
+
module Examples
|
7
|
+
# Comments example
|
8
|
+
class Comments < UI::Container
|
9
|
+
# Includes
|
10
|
+
include UI::Behaviors::Actions
|
11
|
+
include UI::Behaviors::Rest
|
12
|
+
|
13
|
+
# Extends
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
tag 'ui-comments'
|
17
|
+
|
18
|
+
# REST
|
19
|
+
rest url: 'http://localhost:3000/comments'
|
20
|
+
|
21
|
+
# Style
|
22
|
+
style margin: '2em auto',
|
23
|
+
maxWidth: 42.em,
|
24
|
+
fontSize: 16.px,
|
25
|
+
'> ui-container' => { borderBottom: -> { "#{(theme.border_size / 1.5).em} dashed #{dampen colors.background, 0.05}" },
|
26
|
+
paddingBottom: -> { theme.spacing.em },
|
27
|
+
marginBottom: -> { theme.spacing.em },
|
28
|
+
'ui-button' => { maxWidth: 10.em,
|
29
|
+
alignSelf: 'flex-end' },
|
30
|
+
textarea: { border: -> { "#{(theme.border_size / 1.5).em} solid #{dampen colors.background, 0.05}" },
|
31
|
+
boxSizing: 'border-box',
|
32
|
+
minHeight: 7.em,
|
33
|
+
flex: 1 } }
|
34
|
+
|
35
|
+
# Components
|
36
|
+
component :form, UI::Container do
|
37
|
+
component :title, UI::Title, text: 'Leave a comment', flex: '0 0 auto'
|
38
|
+
component :box, UI::Container, direction: :row do
|
39
|
+
component :image, UI::Image, src: Lorem.avatar, width: 4.em, height: 4.em
|
40
|
+
component :input, UI::Textarea, spellcheck: false
|
41
|
+
end
|
42
|
+
component :button, UI::Button, text: 'Comment', action: :add
|
43
|
+
end
|
44
|
+
component :list, List, base: Comment
|
45
|
+
|
46
|
+
# Delegations
|
47
|
+
def_delegators :form, :box
|
48
|
+
def_delegators :box, :image, :input
|
49
|
+
|
50
|
+
# Events
|
51
|
+
on :refresh, :load
|
52
|
+
|
53
|
+
# Adds a new comment
|
54
|
+
def add
|
55
|
+
return if input.value.empty?
|
56
|
+
|
57
|
+
data = {
|
58
|
+
id: SecureRandom.uuid,
|
59
|
+
date: Time.now,
|
60
|
+
votes: 0,
|
61
|
+
user: {
|
62
|
+
name: name,
|
63
|
+
image: image.src
|
64
|
+
},
|
65
|
+
body: input.value.split("\n").map { |paragraph| "<p>#{paragraph}</p>" }.join('')
|
66
|
+
}
|
67
|
+
|
68
|
+
create data do
|
69
|
+
input.value = ''
|
70
|
+
load
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Starts to reply by focusing the input field
|
75
|
+
def reply
|
76
|
+
input.focus
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the current users name
|
80
|
+
#
|
81
|
+
# @return [String] The name
|
82
|
+
def name
|
83
|
+
@name ||= Lorem.name
|
84
|
+
end
|
85
|
+
|
86
|
+
# Loads comments from the server
|
87
|
+
def load
|
88
|
+
all do |items|
|
89
|
+
@list.items = items.reverse
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Examples
|
2
|
+
class Comments < UI::Container
|
3
|
+
# Comment footer
|
4
|
+
class Footer < UI::Container
|
5
|
+
# Tagname
|
6
|
+
tag 'ui-comment-footer'
|
7
|
+
|
8
|
+
# Style
|
9
|
+
style color: -> { dampen colors.background, 0.3 },
|
10
|
+
span: { opacity: 0.8 },
|
11
|
+
fontSize: 0.8.em,
|
12
|
+
'&[direction=row]:not([compact]) > * + *' => { marginLeft: -> { (theme.spacing / 2).em } },
|
13
|
+
'ui-label' => { marginRight: -> { (theme.spacing / 2).em } },
|
14
|
+
'ui-action' => { display: :flex, fontWeight: 600 }
|
15
|
+
|
16
|
+
# Components
|
17
|
+
component :vote_up, UI::Action, direction: :row, action: :vote_up do
|
18
|
+
component :label, UI::Label, text: 0
|
19
|
+
component :icon, UI::Icon, glyph: 'chevron-up'
|
20
|
+
end
|
21
|
+
component :span, :span, text: '|'
|
22
|
+
component :vote_down, UI::Action, action: :vote_down do
|
23
|
+
component :icon, UI::Icon, glyph: 'chevron-down'
|
24
|
+
end
|
25
|
+
component :span, :span, text: '|'
|
26
|
+
component :reply, UI::Action, text: 'Reply', action: :reply
|
27
|
+
|
28
|
+
# Renders the component
|
29
|
+
#
|
30
|
+
# @param data [Hash] The data
|
31
|
+
def render(data)
|
32
|
+
@vote_up.label.text = data[:votes]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Examples
|
2
|
+
class Comments < UI::Container
|
3
|
+
# Comment header
|
4
|
+
class Header < UI::Container
|
5
|
+
# Tagname
|
6
|
+
tag 'ui-comment-header'
|
7
|
+
|
8
|
+
# Style
|
9
|
+
style 'ui-time' => { fontSize: 0.85.em,
|
10
|
+
lineHeight: 1.5,
|
11
|
+
opacity: 0.4,
|
12
|
+
'&:before' => { content: '"-"',
|
13
|
+
marginRight: -> { (theme.spacing / 2).em } } },
|
14
|
+
'&[direction=row]:not([compact]) > * + *' => { marginLeft: -> { (theme.spacing / 2).em } },
|
15
|
+
'ui-label' => { fontWeight: 600 },
|
16
|
+
'ui-action' => { display: :none }
|
17
|
+
|
18
|
+
# Components
|
19
|
+
component :user, UI::Label
|
20
|
+
component :date, UI::Time, from_now: true
|
21
|
+
component :spacer, UI::Base, flex: 1
|
22
|
+
component :action, UI::Action, action: :confirm_destroy! do
|
23
|
+
component :icon, UI::Icon, glyph: 'android-close'
|
24
|
+
end
|
25
|
+
|
26
|
+
# Renders the component
|
27
|
+
#
|
28
|
+
# @param data [Hash] The data
|
29
|
+
def render(data)
|
30
|
+
@date.value = data[:date]
|
31
|
+
@user.text = data[:user][:name]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require_relative 'details'
|
2
|
+
require_relative 'sidebar'
|
3
|
+
|
4
|
+
module Examples
|
5
|
+
# Contacts example
|
6
|
+
class Contacts < UI::Container
|
7
|
+
include UI::Behaviors::Actions
|
8
|
+
include UI::Behaviors::Render
|
9
|
+
include UI::Behaviors::Rest
|
10
|
+
include UI::Behaviors::State
|
11
|
+
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
rest url: 'http://localhost:3000/contacts'
|
15
|
+
|
16
|
+
component :sidebar, Sidebar, flex: '0 0 20em'
|
17
|
+
component :details, Details, flex: 1
|
18
|
+
|
19
|
+
style fontSize: 14.px,
|
20
|
+
width: 67.5.em,
|
21
|
+
margin: '0 auto',
|
22
|
+
height: 57.5.em,
|
23
|
+
padding: -> { theme.spacing.em },
|
24
|
+
boxSizing: 'border-box'
|
25
|
+
|
26
|
+
render :render!
|
27
|
+
|
28
|
+
def_delegators :class, :storage
|
29
|
+
|
30
|
+
on :selected_change, :select
|
31
|
+
on :input, 'ui-sidebar input', :render
|
32
|
+
on :refresh, :refresh
|
33
|
+
on :destroyed, :destroyed
|
34
|
+
|
35
|
+
state_changed :state_changed
|
36
|
+
|
37
|
+
# Initializes the component
|
38
|
+
def initialize
|
39
|
+
super
|
40
|
+
@items = []
|
41
|
+
self[:direction] = :row
|
42
|
+
end
|
43
|
+
|
44
|
+
# Handles state change
|
45
|
+
def state_changed
|
46
|
+
load state
|
47
|
+
render!
|
48
|
+
end
|
49
|
+
|
50
|
+
# Adds a new contact
|
51
|
+
def add
|
52
|
+
data = {
|
53
|
+
id: SecureRandom.uuid
|
54
|
+
}
|
55
|
+
|
56
|
+
create data do |item|
|
57
|
+
refresh do
|
58
|
+
@sidebar.input.value = ''
|
59
|
+
render!
|
60
|
+
self.state = item[:id]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Handles item selection from the list
|
66
|
+
def select
|
67
|
+
self.state = @sidebar.selected.data[:id]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Loads the contact with the given id
|
71
|
+
#
|
72
|
+
# @param id [String] The ID
|
73
|
+
def load(id)
|
74
|
+
@details.load id
|
75
|
+
render!
|
76
|
+
end
|
77
|
+
|
78
|
+
# Handles the destroyed event
|
79
|
+
#
|
80
|
+
# @param event [DOM::Event] The event
|
81
|
+
def destroyed(event)
|
82
|
+
self.state = nil if event.target.data[:id] == state
|
83
|
+
refresh
|
84
|
+
end
|
85
|
+
|
86
|
+
# Refreshes the list
|
87
|
+
def refresh
|
88
|
+
all do |items|
|
89
|
+
@items = items
|
90
|
+
render { yield if block_given? }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Renders the list
|
95
|
+
def render!
|
96
|
+
@sidebar.items = @items.select { |item| item[:name].to_s.match Regexp.new(@sidebar.input.value || '.*', 'i') }
|
97
|
+
@sidebar.select state
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Examples
|
2
|
+
class Contacts < UI::Container
|
3
|
+
# Details
|
4
|
+
class Details < UI::Box
|
5
|
+
include UI::Behaviors::Confirmation
|
6
|
+
include UI::Behaviors::Serialize
|
7
|
+
include UI::Behaviors::Actions
|
8
|
+
include UI::Behaviors::Rest
|
9
|
+
|
10
|
+
tag 'ui-details'
|
11
|
+
|
12
|
+
rest url: 'http://localhost:3000/contacts'
|
13
|
+
|
14
|
+
style 'ui-image' => { margin: -> { theme.spacing.em },
|
15
|
+
height: 15.em,
|
16
|
+
width: 15.em },
|
17
|
+
'ui-container' => { padding: -> { theme.spacing.em } },
|
18
|
+
'ui-container ui-container' => { maxWidth: 30.em },
|
19
|
+
'ui-label' => { fontWeight: 600 },
|
20
|
+
'input' => { fontSize: 1.2.em },
|
21
|
+
'ui-loader' => { fontSize: 2.em },
|
22
|
+
'&.empty' => {
|
23
|
+
'> ui-container, > ui-button' => {
|
24
|
+
display: :none
|
25
|
+
},
|
26
|
+
'&:after' => {
|
27
|
+
content: "'No contact selected!'",
|
28
|
+
padding: -> { (theme.spacing * 3).em },
|
29
|
+
textAlign: :center,
|
30
|
+
fontSize: 2.em,
|
31
|
+
opacity: 0.25
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
component :title, UI::Title, text: 'Contact Details'
|
36
|
+
|
37
|
+
component :box, UI::Container, flex: 1, direction: :row do
|
38
|
+
component :image, UI::Image
|
39
|
+
component :form, UI::Container, flex: 1 do
|
40
|
+
component :label, UI::Label, text: 'Full Name:'
|
41
|
+
component :input, UI::Input, placeholder: 'Tony Stark', name: :name
|
42
|
+
component :label, UI::Label, text: 'E-mail:'
|
43
|
+
component :input, UI::Input, placeholder: 'tony@stark-industries.com', name: :email
|
44
|
+
component :label, UI::Label, text: 'Address:'
|
45
|
+
component :input, UI::Input, placeholder: '10880 Malibu Point, Malibu, Calif', name: :address
|
46
|
+
component :label, UI::Label, text: 'Phone:'
|
47
|
+
component :input, UI::Input, placeholder: '+1-202-555-0160', name: :phone
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
component :button, UI::Button, type: 'danger', text: 'Remove Contact', action: :confirm_destroy!
|
52
|
+
|
53
|
+
confirmation :destroy!, 'Are you sure you want to remove this contact?'
|
54
|
+
|
55
|
+
on :change, :save
|
56
|
+
|
57
|
+
# Loads the contact with the given id
|
58
|
+
#
|
59
|
+
# @param id [String] The ID
|
60
|
+
def load(id)
|
61
|
+
return add_class :empty if id.empty?
|
62
|
+
request :get, id do |data|
|
63
|
+
break add_class :empty unless data
|
64
|
+
super data
|
65
|
+
remove_class :empty
|
66
|
+
render
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Saves the contact
|
71
|
+
def save
|
72
|
+
update data do
|
73
|
+
@data.merge! data
|
74
|
+
render
|
75
|
+
trigger :refresh
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Destroys the contact
|
80
|
+
def destroy!
|
81
|
+
destroy do
|
82
|
+
trigger :destroyed
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Renders the contact image
|
87
|
+
def render
|
88
|
+
@box.image.src = 'http://www.gravatar.com/avatar/' + `md5(#{data[:email] || ''})` + '?s=200&d=identicon'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|