chr 0.2.1 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/Gruntfile.coffee +50 -16
- data/app/assets/javascripts/chr.coffee +9 -16
- data/app/assets/javascripts/chr/core/chr.coffee +38 -20
- data/app/assets/javascripts/chr/core/item.coffee +30 -24
- data/app/assets/javascripts/chr/core/list.coffee +30 -60
- data/app/assets/javascripts/chr/core/list_config.coffee +65 -0
- data/app/assets/javascripts/chr/core/list_pagination.coffee +27 -0
- data/app/assets/javascripts/chr/core/list_reorder.coffee +75 -0
- data/app/assets/javascripts/chr/core/list_search.coffee +41 -0
- data/app/assets/javascripts/chr/core/module.coffee +55 -32
- data/app/assets/javascripts/chr/core/utils.coffee +34 -13
- data/app/assets/javascripts/chr/core/view.coffee +70 -97
- data/app/assets/javascripts/chr/form/form.coffee +63 -49
- data/app/assets/javascripts/chr/form/input-checkbox.coffee +40 -27
- data/app/assets/javascripts/chr/form/input-color.coffee +26 -8
- data/app/assets/javascripts/chr/form/input-date.coffee +0 -0
- data/app/assets/javascripts/chr/form/input-file.coffee +81 -46
- data/app/assets/javascripts/chr/form/input-form.coffee +162 -0
- data/app/assets/javascripts/chr/form/input-form_reorder.coffee +67 -0
- data/app/assets/javascripts/chr/form/input-hidden.coffee +27 -11
- data/app/assets/javascripts/chr/form/input-list.coffee +60 -56
- data/app/assets/javascripts/chr/form/input-list_reorder.coffee +37 -0
- data/app/assets/javascripts/chr/form/input-password.coffee +31 -0
- data/app/assets/javascripts/chr/form/input-select.coffee +61 -35
- data/app/assets/javascripts/chr/form/input-string.coffee +55 -25
- data/app/assets/javascripts/chr/form/input-text.coffee +22 -5
- data/app/assets/javascripts/chr/store/mongosteen-array-store.coffee +1 -1
- data/app/assets/javascripts/chr/vendor/ace.js +18280 -0
- data/app/assets/javascripts/chr/vendor/marked.js +1272 -0
- data/app/assets/javascripts/chr/vendor/mode-html.js +2436 -0
- data/app/assets/javascripts/chr/vendor/mode-markdown.js +2820 -0
- data/app/assets/javascripts/chr/vendor/redactor.fixedtoolbar.js +110 -0
- data/app/assets/javascripts/input-html.coffee +78 -0
- data/app/assets/javascripts/input-markdown.coffee +88 -0
- data/app/assets/javascripts/input-redactor.coffee +66 -0
- data/app/assets/stylesheets/_chr.scss +6 -6
- data/app/assets/stylesheets/_input-redactor.scss +34 -0
- data/app/assets/stylesheets/core/_mixins.scss +75 -0
- data/app/assets/stylesheets/form/_input-checkbox.scss +18 -0
- data/app/assets/stylesheets/form/{_input_color.scss → _input-color.scss} +0 -0
- data/app/assets/stylesheets/form/{_input_file.scss → _input-file.scss} +1 -0
- data/app/assets/stylesheets/form/{_nested_form.scss → _input-form.scss} +0 -0
- data/app/assets/stylesheets/form/{_input_list.scss → _input-list.scss} +0 -0
- data/app/assets/stylesheets/form/_input-string.scss +8 -0
- data/bower.json +3 -2
- data/{app/assets/javascripts/chr-dist.js → dist/chr.js} +1472 -1337
- data/dist/input-ace.js +24936 -0
- data/dist/input-redactor.js +156 -0
- data/lib/chr/version.rb +1 -1
- data/package.json +2 -2
- metadata +29 -13
- data/app/assets/javascripts/chr/core/list-pagination.coffee +0 -26
- data/app/assets/javascripts/chr/core/list-reorder.coffee +0 -70
- data/app/assets/javascripts/chr/core/list-search.coffee +0 -37
- data/app/assets/javascripts/chr/form/nested-form.coffee +0 -164
- data/app/assets/stylesheets/form/_input_checkbox.scss +0 -91
- data/app/assets/stylesheets/form/_input_string.scss +0 -8
@@ -0,0 +1,65 @@
|
|
1
|
+
|
2
|
+
# -----------------------------------------------------------------------------
|
3
|
+
# LIST CONFIG
|
4
|
+
# Methods for processing:
|
5
|
+
# - @config.items
|
6
|
+
# - @config.arrayStore
|
7
|
+
# - @config.objectStore
|
8
|
+
# -----------------------------------------------------------------------------
|
9
|
+
|
10
|
+
@listConfig =
|
11
|
+
# PRIVATE ===============================================
|
12
|
+
|
13
|
+
_process_config_items: ->
|
14
|
+
for slug, config of @config.items
|
15
|
+
object = { _id: slug, _title: config.title ? slug.titleize() }
|
16
|
+
|
17
|
+
# There might be some cases when we need this:
|
18
|
+
#if config.objectStore
|
19
|
+
# $.extend(object, config.objectStore.get())
|
20
|
+
|
21
|
+
if config.items or config.arrayStore
|
22
|
+
@module.addNestedList(slug, config, this)
|
23
|
+
|
24
|
+
@_add_item("#/#{ @path }/#{ slug }", object, 0, config)
|
25
|
+
@configItemsCount += 1
|
26
|
+
|
27
|
+
|
28
|
+
_bind_config_array_store: ->
|
29
|
+
# item added
|
30
|
+
@config.arrayStore.on 'object_added', (e, data) =>
|
31
|
+
@_add_item("#/#{ @path }/view/#{ data.object._id }", data.object, data.position, @config)
|
32
|
+
|
33
|
+
if @config.objects
|
34
|
+
@config.arrayStore.addObjects(@config.objects)
|
35
|
+
|
36
|
+
# item updated
|
37
|
+
@config.arrayStore.on 'object_changed', (e, data) =>
|
38
|
+
item = @items[data.object._id]
|
39
|
+
if item then item.render() ; @_update_item_position(item, data.position)
|
40
|
+
|
41
|
+
# item removed
|
42
|
+
@config.arrayStore.on 'object_removed', (e, data) =>
|
43
|
+
item = @items[data.object_id]
|
44
|
+
if item then item.destroy() ; delete @items[data.object_id]
|
45
|
+
|
46
|
+
# items loaded
|
47
|
+
@config.arrayStore.on 'objects_added', (e, data) =>
|
48
|
+
@_hide_spinner()
|
49
|
+
@_set_active_item()
|
50
|
+
|
51
|
+
if @config.arrayStore.pagination
|
52
|
+
@_bind_pagination()
|
53
|
+
|
54
|
+
if @config.arrayStore.searchable
|
55
|
+
@_bind_search(this)
|
56
|
+
|
57
|
+
if @config.arrayStore.reorderable
|
58
|
+
@_bind_reorder(this)
|
59
|
+
|
60
|
+
|
61
|
+
_bind_config_object_store: ->
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
# LIST PAGINATION
|
3
|
+
# todo:
|
4
|
+
# - trigger onScroll event only when scrolling down
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
|
7
|
+
@listPagination =
|
8
|
+
# PRIVATE ===============================================
|
9
|
+
|
10
|
+
_bind_pagination: ->
|
11
|
+
arrayStore = @config.arrayStore
|
12
|
+
@$items.scroll (e) =>
|
13
|
+
if ! arrayStore.dataFetchLock
|
14
|
+
# TODO: update this logic as it's not reliable when items has different height
|
15
|
+
$listChildren = @$items.children()
|
16
|
+
listChildrenCount = $listChildren.length
|
17
|
+
listFirstChildHeight = $listChildren.first().outerHeight()
|
18
|
+
listHeight = listChildrenCount * listFirstChildHeight
|
19
|
+
viewHeight = @$el.height()
|
20
|
+
|
21
|
+
if listHeight < (viewHeight + e.target.scrollTop + 100)
|
22
|
+
@_show_spinner()
|
23
|
+
arrayStore.load()
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
# LIST REORDER
|
3
|
+
# -----------------------------------------------------------------------------
|
4
|
+
#
|
5
|
+
# Dependencies:
|
6
|
+
#= require ../vendor/slip
|
7
|
+
#
|
8
|
+
# -----------------------------------------------------------------------------
|
9
|
+
|
10
|
+
@listReorder =
|
11
|
+
# PRIVATE ===============================================
|
12
|
+
|
13
|
+
_bind_reorder: (listEl) ->
|
14
|
+
items = listEl.items
|
15
|
+
list = listEl.$items.get(0)
|
16
|
+
arrayStore = listEl.config.arrayStore
|
17
|
+
|
18
|
+
config = arrayStore.reorderable
|
19
|
+
|
20
|
+
# this is optimistic scenario when assumes that all positions are different
|
21
|
+
_getObjectNewPosition = (el) ->
|
22
|
+
$el =$ el
|
23
|
+
|
24
|
+
nextObjectId = $el.next().attr('data-id')
|
25
|
+
prevObjectId = $el.prev().attr('data-id')
|
26
|
+
nextObjectPosition = 0
|
27
|
+
prevObjectPosition = 0
|
28
|
+
|
29
|
+
if prevObjectId
|
30
|
+
prevObjectPosition = items[prevObjectId].position()
|
31
|
+
|
32
|
+
if nextObjectId
|
33
|
+
nextObjectPosition = items[nextObjectId].position()
|
34
|
+
|
35
|
+
if arrayStore.sortReverse
|
36
|
+
newPosition = nextObjectPosition + Math.abs(nextObjectPosition - prevObjectPosition) / 2.0
|
37
|
+
else
|
38
|
+
newPosition = prevObjectPosition + Math.abs(nextObjectPosition - prevObjectPosition) / 2.0
|
39
|
+
|
40
|
+
return newPosition
|
41
|
+
|
42
|
+
new Slip(list)
|
43
|
+
|
44
|
+
list.addEventListener 'slip:beforeswipe', (e) -> e.preventDefault()
|
45
|
+
|
46
|
+
list.addEventListener 'slip:beforewait', ((e) ->
|
47
|
+
if $(e.target).hasClass("icon-reorder") then e.preventDefault()
|
48
|
+
), false
|
49
|
+
|
50
|
+
list.addEventListener 'slip:beforereorder', ((e) ->
|
51
|
+
if not $(e.target).hasClass("icon-reorder") then e.preventDefault()
|
52
|
+
), false
|
53
|
+
|
54
|
+
list.addEventListener 'slip:reorder', ((e) =>
|
55
|
+
# when `e.detail.insertBefore` is null, item put to the end of the list.
|
56
|
+
e.target.parentNode.insertBefore(e.target, e.detail.insertBefore)
|
57
|
+
|
58
|
+
objectPositionValue = _getObjectNewPosition(e.target)
|
59
|
+
objectId = $(e.target).attr('data-id')
|
60
|
+
value = {}
|
61
|
+
value["[#{arrayStore.sortBy}"] = "#{ objectPositionValue }"
|
62
|
+
|
63
|
+
arrayStore.update objectId, value,
|
64
|
+
# error handling
|
65
|
+
onSuccess: (object) => ;
|
66
|
+
onError: (errors) => ;
|
67
|
+
|
68
|
+
return false
|
69
|
+
), false
|
70
|
+
|
71
|
+
$(list).addClass 'reorderable'
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
# LIST SEARCH
|
3
|
+
# -----------------------------------------------------------------------------
|
4
|
+
|
5
|
+
@listSearch =
|
6
|
+
# PRIVATE ===============================================
|
7
|
+
|
8
|
+
_bind_search: (listEl) ->
|
9
|
+
$input = listEl.$search
|
10
|
+
arrayStore = listEl.config.arrayStore
|
11
|
+
|
12
|
+
search = (input) ->
|
13
|
+
query = $(input).val()
|
14
|
+
listEl._show_spinner()
|
15
|
+
arrayStore.search(query)
|
16
|
+
|
17
|
+
show = ->
|
18
|
+
listEl.$el.addClass 'list-search'
|
19
|
+
$input.find('input').focus()
|
20
|
+
|
21
|
+
cancel = ->
|
22
|
+
listEl.$el.removeClass 'list-search'
|
23
|
+
$input.find('input').val('')
|
24
|
+
listEl._show_spinner()
|
25
|
+
arrayStore.reset()
|
26
|
+
|
27
|
+
$input.show()
|
28
|
+
|
29
|
+
$input.on 'keyup', 'input', (e) =>
|
30
|
+
if e.keyCode == 27 # esc
|
31
|
+
return cancel()
|
32
|
+
|
33
|
+
if e.keyCode == 13 # enter
|
34
|
+
return search(e.target)
|
35
|
+
|
36
|
+
$input.on 'click', '.icon', (e) => e.preventDefault() ; show()
|
37
|
+
$input.on 'click', '.cancel', (e) => e.preventDefault() ; cancel()
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
|
@@ -8,11 +8,25 @@
|
|
8
8
|
|
9
9
|
# -----------------------------------------------------------------------------
|
10
10
|
# MODULE
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
# Config options:
|
13
13
|
# title - title used for menu and root list header
|
14
14
|
# showNestedListsAside - show module root list on the left and all nested
|
15
15
|
# lists on the right side for desktop
|
16
|
+
#
|
17
|
+
# Public methods:
|
18
|
+
# addNestedList (listName, config, parentList)
|
19
|
+
# showNestedList (listName, animate=false)
|
20
|
+
# hideNestedLists (exceptList)
|
21
|
+
# visibleNestedListShownWithParent ()
|
22
|
+
# showRootList()
|
23
|
+
# hideActiveList (animate=false)
|
24
|
+
# showView (object, config, title, animate=false)
|
25
|
+
# showViewByObjectId (objectId, config, title, animate=false)
|
26
|
+
# destroyView ()
|
27
|
+
# show ()
|
28
|
+
# hide (animate=false)
|
29
|
+
#
|
16
30
|
# -----------------------------------------------------------------------------
|
17
31
|
class @Module
|
18
32
|
constructor: (@chr, @name, @config) ->
|
@@ -34,7 +48,7 @@ class @Module
|
|
34
48
|
@$el.addClass 'first-list-aside'
|
35
49
|
# jump to first nested list on menu click
|
36
50
|
firstNestedList = _firstNonEmptyValue(@nestedLists)
|
37
|
-
if !
|
51
|
+
if ! @chr.isMobile() && firstNestedList
|
38
52
|
menuPath += "/#{ firstNestedList.name }"
|
39
53
|
|
40
54
|
@chr.addMenuItem(menuPath, menuTitle)
|
@@ -42,6 +56,8 @@ class @Module
|
|
42
56
|
@config.onModuleInit?(this)
|
43
57
|
|
44
58
|
|
59
|
+
# PRIVATE ===============================================
|
60
|
+
|
45
61
|
# update list data if it's not visible, e.g. for update action we do not
|
46
62
|
# update whole list, this method is called before active list is shown.
|
47
63
|
_update_active_list_items: ->
|
@@ -55,10 +71,46 @@ class @Module
|
|
55
71
|
currentList.path
|
56
72
|
|
57
73
|
|
74
|
+
# PUBLIC ================================================
|
75
|
+
|
58
76
|
addNestedList: (listName, config, parentList) ->
|
59
77
|
@nestedLists[listName] = new List(this, listName, config, parentList)
|
60
78
|
|
61
79
|
|
80
|
+
# shows one of nested lists, with or without animation
|
81
|
+
showNestedList: (listName, animate=false) ->
|
82
|
+
listToShow = @nestedLists[listName]
|
83
|
+
|
84
|
+
if listToShow.showWithParent
|
85
|
+
# list works as view, never becomes active
|
86
|
+
listToShow.updateItems()
|
87
|
+
listToShow.show animate, => @hideNestedLists(exceptList=listName)
|
88
|
+
|
89
|
+
else
|
90
|
+
@activeList = listToShow
|
91
|
+
@_update_active_list_items()
|
92
|
+
@activeList.show(animate)
|
93
|
+
|
94
|
+
# hide view
|
95
|
+
if animate and @view then @view.$el.fadeOut $.fx.speeds._default, => @destroyView()
|
96
|
+
|
97
|
+
|
98
|
+
hideNestedLists: (exceptList) ->
|
99
|
+
@activeList = @rootList
|
100
|
+
list.hide() for key, list of @nestedLists when key isnt exceptList
|
101
|
+
|
102
|
+
|
103
|
+
visibleNestedListShownWithParent: ->
|
104
|
+
for key, list of @nestedLists
|
105
|
+
if list.isVisible() && list.showWithParent then return list
|
106
|
+
|
107
|
+
|
108
|
+
showRootList: () ->
|
109
|
+
@destroyView()
|
110
|
+
while @activeList != @rootList
|
111
|
+
@hideActiveList(false)
|
112
|
+
|
113
|
+
|
62
114
|
hideActiveList: (animate=false)->
|
63
115
|
if animate then @activeList.$el.fadeOut() else @activeList.$el.hide()
|
64
116
|
@activeList = @activeList.parentList
|
@@ -105,34 +157,5 @@ class @Module
|
|
105
157
|
@$el.hide()
|
106
158
|
|
107
159
|
|
108
|
-
# shows one of nested lists, with or without animation
|
109
|
-
showNestedList: (listName, animate=false) ->
|
110
|
-
listToShow = @nestedLists[listName]
|
111
|
-
|
112
|
-
if listToShow.showWithParent
|
113
|
-
# list works as view, never becomes active
|
114
|
-
listToShow.updateItems()
|
115
|
-
listToShow.show animate, => @hideNestedLists(exceptList=listName)
|
116
|
-
|
117
|
-
else
|
118
|
-
@activeList = listToShow
|
119
|
-
@_update_active_list_items()
|
120
|
-
@activeList.show(animate)
|
121
|
-
|
122
|
-
# hide view
|
123
|
-
if animate and @view then @view.$el.fadeOut $.fx.speeds._default, => @destroyView()
|
124
|
-
|
125
|
-
|
126
|
-
hideNestedLists: (exceptList) ->
|
127
|
-
@activeList = @rootList
|
128
|
-
list.hide() for key, list of @nestedLists when key isnt exceptList
|
129
|
-
|
130
|
-
|
131
|
-
# returns visible nested list that acts as view
|
132
|
-
visibleNestedListShownWithParent: ->
|
133
|
-
for key, list of @nestedLists
|
134
|
-
if list.isVisible() && list.showWithParent then return list
|
135
|
-
|
136
|
-
|
137
160
|
|
138
161
|
|
@@ -1,5 +1,26 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
# Author: Alexander Kravets <alex@slatestudio.com>,
|
3
|
+
# Slate Studio (http://www.slatestudio.com)
|
4
|
+
#
|
5
|
+
# Coding Guide:
|
6
|
+
# https://github.com/thoughtbot/guides/tree/master/style/coffeescript
|
7
|
+
# -----------------------------------------------------------------------------
|
8
|
+
|
1
9
|
# -----------------------------------------------------------------------------
|
2
10
|
# UTILS
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
# Public methods:
|
13
|
+
# _last(array)
|
14
|
+
# _first(array)
|
15
|
+
# _firstNonEmptyValue(hash)
|
16
|
+
# _escapeHtml(string)
|
17
|
+
# String.titleize()
|
18
|
+
# String.reverse()
|
19
|
+
# String.startsWith(str)
|
20
|
+
# String.endsWith(str)
|
21
|
+
# String.plainText()
|
22
|
+
# include(class, hash)
|
23
|
+
# -----------------------------------------------------------------------------
|
3
24
|
|
4
25
|
# _last(array)
|
5
26
|
@_last = (array) -> array[array.length - 1]
|
@@ -10,20 +31,17 @@
|
|
10
31
|
# _firstNonEmptyValue(hash)
|
11
32
|
@_firstNonEmptyValue = (o) -> ((return v if k[0] != '_' and v and v != '') for k, v of o) ; return null
|
12
33
|
|
13
|
-
# _stripHtml(string)
|
14
|
-
@_stripHtml = (string) -> String(string).replace(/<\/?[^>]+(>|$)/g, "")
|
15
|
-
|
16
34
|
# _escapeHtml(string)
|
17
35
|
@_entityMap = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/' }
|
18
36
|
@_escapeHtml = (string) -> String(string).replace /[&<>"'\/]/g, (s) -> _entityMap[s]
|
19
37
|
|
20
38
|
# String.titleize
|
21
39
|
if typeof String.prototype.titleize != 'function'
|
22
|
-
String.prototype.titleize =
|
40
|
+
String.prototype.titleize = -> return this.replace(/_/g, ' ').replace(/\b./g, ((m) -> m.toUpperCase()))
|
23
41
|
|
24
42
|
# String.reverse
|
25
43
|
if typeof String.prototype.reverse != 'function'
|
26
|
-
String.prototype.reverse =
|
44
|
+
String.prototype.reverse = -> return this.split("").reverse().join("")
|
27
45
|
|
28
46
|
# String.startsWith
|
29
47
|
if typeof String.prototype.startsWith != 'function'
|
@@ -33,17 +51,20 @@ if typeof String.prototype.startsWith != 'function'
|
|
33
51
|
if typeof String.prototype.endsWith != 'function'
|
34
52
|
String.prototype.endsWith = (str) -> return this.slice(this.length - str.length, this.length) == str
|
35
53
|
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
# Helps to figure out how many list items fits screen height
|
40
|
-
@_itemsPerScreen = -> itemHeight = 60 ; return Math.ceil($(window).height() / itemHeight)
|
41
|
-
@_itemsPerPageRequest = _itemsPerScreen() * 2
|
54
|
+
# String.plainText
|
55
|
+
if typeof String.prototype.plainText != 'function'
|
56
|
+
String.prototype.plainText = () -> return $("<div>#{ this }</div>").text()
|
42
57
|
|
43
|
-
# Check if running on mobile
|
44
|
-
@_isMobile = -> $(window).width() < 760
|
45
58
|
|
46
59
|
# -----------------------------------------------------------------------------
|
60
|
+
# Mixins: http://arcturo.github.io/library/coffeescript/03_classes.html
|
61
|
+
# -----------------------------------------------------------------------------
|
62
|
+
@extend = (obj, mixin) ->
|
63
|
+
obj[name] = method for name, method of mixin
|
64
|
+
return obj
|
65
|
+
|
66
|
+
@include = (klass, mixin) ->
|
67
|
+
extend(klass.prototype, mixin)
|
47
68
|
|
48
69
|
|
49
70
|
|
@@ -9,11 +9,15 @@
|
|
9
9
|
# -----------------------------------------------------------------------------
|
10
10
|
# VIEW
|
11
11
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
12
|
+
# config options:
|
13
|
+
# formClass - custom form class to be used
|
14
|
+
# formSchema - form schema for object, autogenerated if missing
|
15
|
+
# disableDelete - do not add delete button below the form
|
16
|
+
# disableSave - do not add save button in header
|
17
|
+
# fullsizeView — use fullsize layout in desktop mode
|
18
|
+
# onViewShow - on show callback
|
19
|
+
# onShowAnimation - animation method to be used on show
|
20
|
+
# onCloseAnimation - animation method to be used on close
|
17
21
|
#
|
18
22
|
# public methods:
|
19
23
|
# show(animate, callback)
|
@@ -24,151 +28,120 @@ class @View
|
|
24
28
|
constructor: (@module, @config, @closePath, @object, @title) ->
|
25
29
|
@store = @config.arrayStore ? @config.objectStore
|
26
30
|
|
27
|
-
@$el =$ "<section class='view #{ @module.name }'>"
|
28
|
-
@$el.hide()
|
31
|
+
@$el =$ "<section class='view #{ @module.name }' style='display:none;'>"
|
29
32
|
|
33
|
+
# fullsize
|
30
34
|
if @config.fullsizeView
|
31
35
|
@$el.addClass 'fullsize'
|
32
36
|
|
37
|
+
# animations
|
38
|
+
@onShowAnimation = @config.onShowAnimation
|
39
|
+
@onCloseAnimation = @config.onCloseAnimation
|
40
|
+
@onShowAnimation ?= (callback) => @$el.fadeIn $.fx.speeds._default, -> callback()
|
41
|
+
@onCloseAnimation ?= (callback) => @$el.fadeOut $.fx.speeds._default, -> callback()
|
42
|
+
|
33
43
|
# header
|
34
44
|
@$header =$ "<header></header>"
|
35
|
-
@$el.append @$header
|
36
|
-
|
37
45
|
@$title =$ "<div class='title'></div>"
|
38
46
|
@$header.append @$title
|
47
|
+
@$el.append @$header
|
48
|
+
@_set_title()
|
39
49
|
|
40
|
-
# close
|
50
|
+
# close
|
41
51
|
@$closeBtn =$ "<a href='#/#{ @closePath }' class='close silent'>Close</a>"
|
42
|
-
@$closeBtn.on 'click', (e) => @
|
52
|
+
@$closeBtn.on 'click', (e) => @_close(e)
|
43
53
|
@$header.append @$closeBtn
|
44
54
|
|
45
|
-
# save
|
55
|
+
# save
|
46
56
|
unless @config.disableSave
|
47
57
|
@$saveBtn =$ "<a href='#' class='save'>Save</a>"
|
48
|
-
@$saveBtn.on 'click', (e) => @
|
58
|
+
@$saveBtn.on 'click', (e) => @_save(e)
|
49
59
|
@$header.append @$saveBtn
|
50
60
|
|
51
|
-
|
52
|
-
|
61
|
+
# form
|
62
|
+
@form = new (@config.formClass ? Form)(@object, @config)
|
63
|
+
@$el.append @form.$el
|
64
|
+
@_add_form_delete_button()
|
53
65
|
|
54
|
-
_render: ->
|
55
|
-
@_update_title()
|
56
|
-
@_render_form()
|
57
66
|
|
67
|
+
# PRIVATE ===============================================
|
58
68
|
|
59
|
-
|
69
|
+
_set_title: (reset=false) ->
|
70
|
+
if reset && @config.arrayStore then @title = null
|
60
71
|
title = @title
|
61
72
|
title ?= @object[@config.itemTitleField] if @config.itemTitleField
|
62
73
|
title ?= _firstNonEmptyValue(@object)
|
74
|
+
@$title.html(title.plainText())
|
63
75
|
|
64
|
-
if title == "" then title = "No Title"
|
65
|
-
|
66
|
-
# remove html tags from title to do not break layout
|
67
|
-
titleText = $("<div>#{ title }</div>").text()
|
68
|
-
@$title.html(titleText)
|
69
76
|
|
70
|
-
|
71
|
-
|
72
|
-
@form?.destroy()
|
73
|
-
@form = new Form(@object, @config)
|
74
|
-
|
75
|
-
# delete button
|
76
|
-
unless @config.disableDelete or @config.objectStore or @_is_new()
|
77
|
+
_add_form_delete_button: ->
|
78
|
+
unless @config.disableDelete or @config.objectStore or (! @object)
|
77
79
|
@$deleteBtn =$ "<a href='#' class='delete'>Delete</a>"
|
78
|
-
@$deleteBtn.on 'click', (e) => @
|
80
|
+
@$deleteBtn.on 'click', (e) => @_delete(e)
|
79
81
|
@form.$el.append @$deleteBtn
|
80
82
|
|
81
|
-
@$el.append @form.$el
|
82
|
-
|
83
|
-
|
84
|
-
_update_object: (value) ->
|
85
|
-
@_start_saving()
|
86
|
-
@store.update @object._id, value,
|
87
|
-
onSuccess: (@object) =>
|
88
|
-
# add a note here for this line, it's not obvious why it's here,
|
89
|
-
# looks like some logic related to title update
|
90
|
-
if @config.arrayStore then @title = null
|
91
|
-
|
92
|
-
formScrollPosition = @form.$el.scrollTop()
|
93
|
-
@_render()
|
94
|
-
@_initialize_form_plugins()
|
95
|
-
@form.$el.scrollTop(formScrollPosition)
|
96
|
-
|
97
|
-
@_stop_saving()
|
98
83
|
|
99
|
-
|
100
|
-
|
101
|
-
|
84
|
+
_save_success: ->
|
85
|
+
@$el.removeClass('view-saving')
|
86
|
+
@_set_title(true)
|
87
|
+
@form.updateValues(@object)
|
102
88
|
|
103
89
|
|
104
|
-
|
105
|
-
|
106
|
-
# refactor this to subscribe to list event: item_added
|
107
|
-
@store.push value,
|
108
|
-
onSuccess: (object) =>
|
109
|
-
# we need to know when list item is added
|
110
|
-
location.hash = "#/#{ @closePath }/view/#{ object._id }"
|
111
|
-
onError: (errors) =>
|
112
|
-
@_validation_errors('Item were not created.', errors)
|
113
|
-
@_stop_saving()
|
114
|
-
|
115
|
-
_start_saving: ->
|
116
|
-
@$el.addClass('view-saving')
|
117
|
-
|
118
|
-
|
119
|
-
_stop_saving: ->
|
120
|
-
setTimeout ( => @$el.removeClass('view-saving') ), 250
|
121
|
-
|
122
|
-
|
123
|
-
_initialize_form_plugins: ->
|
124
|
-
@form.initializePlugins()
|
125
|
-
@config.onViewShow?(@)
|
126
|
-
|
127
|
-
|
128
|
-
_validation_errors: (message, errors) ->
|
90
|
+
_save_error: (message, validationErrors) ->
|
91
|
+
@$el.removeClass('view-saving')
|
129
92
|
chr.showError(message)
|
130
|
-
@form.showValidationErrors(
|
131
|
-
|
93
|
+
@form.showValidationErrors(validationErrors)
|
132
94
|
|
133
|
-
_is_new: ->
|
134
|
-
! @object
|
135
95
|
|
96
|
+
# EVENTS ================================================
|
136
97
|
|
137
|
-
|
138
|
-
|
98
|
+
_close: (e) ->
|
99
|
+
@onCloseAnimation(=> @destroy())
|
139
100
|
|
140
101
|
|
141
|
-
|
102
|
+
_save: (e) ->
|
142
103
|
e.preventDefault()
|
104
|
+
@$el.addClass('view-saving')
|
105
|
+
|
143
106
|
serializedFormObj = @form.serialize()
|
144
107
|
|
145
108
|
if @object
|
146
|
-
@
|
109
|
+
@store.update @object._id, serializedFormObj,
|
110
|
+
onSuccess: (@object) => @_save_success()
|
111
|
+
onError: (errors) => @_save_error('Changes were not saved.', errors)
|
147
112
|
else
|
148
|
-
@
|
113
|
+
@store.push serializedFormObj,
|
114
|
+
onSuccess: (@object) =>
|
115
|
+
@_save_success()
|
116
|
+
@_add_form_delete_button()
|
117
|
+
chr.updateHash("#/#{ @closePath }/view/#{ @object._id }", true)
|
118
|
+
onError: (errors) => @_save_error('Item were not created.', errors)
|
149
119
|
|
150
120
|
|
151
|
-
|
121
|
+
_delete: (e) ->
|
152
122
|
e.preventDefault()
|
153
123
|
if confirm("Are you sure?")
|
154
|
-
@store.remove
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
@destroy()
|
124
|
+
@store.remove @object._id,
|
125
|
+
onSuccess: => @onCloseAnimation( => chr.updateHash("#/#{ @closePath }", true) ; @destroy() )
|
126
|
+
onError: -> chr.showError('Can\'t delete object.')
|
127
|
+
|
159
128
|
|
129
|
+
# PUBLIC ================================================
|
160
130
|
|
161
131
|
show: (animate, callback) ->
|
132
|
+
after_show = =>
|
133
|
+
callback?()
|
134
|
+
@form.initializePlugins()
|
135
|
+
@config.onViewShow?(@)
|
136
|
+
|
162
137
|
if animate
|
163
|
-
|
164
|
-
@_initialize_form_plugins()
|
165
|
-
callback?())
|
138
|
+
@onShowAnimation(=> after_show())
|
166
139
|
else
|
167
|
-
@$el.show
|
140
|
+
@$el.show(0, => after_show())
|
168
141
|
|
169
142
|
|
170
143
|
destroy: ->
|
171
|
-
@form
|
144
|
+
@form.destroy()
|
172
145
|
@$el.remove()
|
173
146
|
|
174
147
|
|