chr 0.1.5 → 0.2.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.
- checksums.yaml +4 -4
- data/Gruntfile.coffee +6 -3
- data/LICENSE.md +1 -1
- data/README.md +286 -7
- data/app/assets/javascripts/chr-dist.js +1031 -580
- data/app/assets/javascripts/chr.coffee +7 -4
- data/app/assets/javascripts/chr/core/chr.coffee +88 -37
- data/app/assets/javascripts/chr/core/item.coffee +57 -35
- data/app/assets/javascripts/chr/core/{list-scroll.coffee → list-pagination.coffee} +6 -3
- data/app/assets/javascripts/chr/core/list-reorder.coffee +3 -3
- data/app/assets/javascripts/chr/core/list-search.coffee +20 -11
- data/app/assets/javascripts/chr/core/list.coffee +163 -89
- data/app/assets/javascripts/chr/core/module.coffee +75 -35
- data/app/assets/javascripts/chr/core/view.coffee +117 -61
- data/app/assets/javascripts/chr/store/{store.coffee → _array-store.coffee} +53 -106
- data/app/assets/javascripts/chr/store/_object-store.coffee +28 -0
- data/app/assets/javascripts/chr/store/mongosteen-array-store.coffee +199 -0
- data/app/assets/javascripts/chr/store/mongosteen-object-store.coffee +52 -0
- data/app/assets/javascripts/chr/store/rest-array-store.coffee +142 -0
- data/app/assets/javascripts/chr/store/rest-object-store.coffee +79 -0
- data/app/assets/stylesheets/core/_list.scss +20 -25
- data/app/assets/stylesheets/core/_main.scss +3 -4
- data/app/assets/stylesheets/core/_mixins.scss +33 -2
- data/app/assets/stylesheets/core/_responsive.scss +30 -9
- data/app/assets/stylesheets/form/_input_checkbox.scss +18 -14
- data/bower.json +3 -2
- data/chr.gemspec +1 -1
- data/docs/assets.md +0 -0
- data/docs/basics.md +0 -0
- data/docs/bootstrap-data.md +0 -0
- data/docs/custom-inputs.md +0 -0
- data/docs/form.md +0 -0
- data/docs/internals.md +0 -0
- data/docs/nested-forms.md +0 -0
- data/docs/redactor-js.md +0 -0
- data/docs/scopes.md +0 -0
- data/lib/chr/version.rb +1 -1
- data/package.json +1 -1
- metadata +19 -7
- data/app/assets/javascripts/chr/store/store-mongosteen.coffee +0 -111
- data/app/assets/javascripts/chr/store/store-rest.coffee +0 -90
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
#= require ./chr/core/item
|
|
7
7
|
#= require ./chr/core/list
|
|
8
8
|
#= require ./chr/core/list-search
|
|
9
|
-
#= require ./chr/core/list-
|
|
9
|
+
#= require ./chr/core/list-pagination
|
|
10
10
|
#= require ./chr/core/list-reorder
|
|
11
11
|
#= require ./chr/core/view
|
|
12
12
|
#= require ./chr/core/module
|
|
@@ -22,9 +22,12 @@
|
|
|
22
22
|
#= require ./chr/form/input-text
|
|
23
23
|
#= require ./chr/form/nested-form
|
|
24
24
|
|
|
25
|
-
#= require ./chr/store/store
|
|
26
|
-
#= require ./chr/store/store
|
|
27
|
-
#= require ./chr/store/store
|
|
25
|
+
#= require ./chr/store/_object-store
|
|
26
|
+
#= require ./chr/store/_array-store
|
|
27
|
+
#= require ./chr/store/rest-object-store
|
|
28
|
+
#= require ./chr/store/rest-array-store
|
|
29
|
+
#= require ./chr/store/mongosteen-object-store
|
|
30
|
+
#= require ./chr/store/mongosteen-array-store
|
|
28
31
|
|
|
29
32
|
#= require ./chr/core/utils
|
|
30
33
|
#= require ./chr/core/chr
|
|
@@ -1,50 +1,47 @@
|
|
|
1
1
|
# -----------------------------------------------------------------------------
|
|
2
|
-
#
|
|
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
|
+
|
|
9
|
+
# -----------------------------------------------------------------------------
|
|
10
|
+
# CHARACTER
|
|
11
|
+
#
|
|
12
|
+
# public methods:
|
|
13
|
+
# start(@config) - start the character app with configuration
|
|
14
|
+
# addMenuItem(moduleName, title) - add item to main menu
|
|
15
|
+
# showAlert(message) - show alert notification
|
|
16
|
+
# showError(message) - show error message
|
|
17
|
+
#
|
|
3
18
|
# -----------------------------------------------------------------------------
|
|
4
19
|
class @Chr
|
|
5
|
-
constructor:
|
|
20
|
+
constructor: ->
|
|
6
21
|
@modules = {}
|
|
7
22
|
|
|
8
|
-
@$el =$ (@config.selector ? 'body')
|
|
9
|
-
@$navBar =$ "<nav class='sidebar'>"
|
|
10
|
-
@$mainMenu =$ "<div class='menu'>"
|
|
11
23
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@modules[name] = new Module(this, name, config) for name, config of @config.modules
|
|
24
|
+
_unset_active_menu_items: ->
|
|
25
|
+
$('.sidebar .menu a.active').removeClass('active')
|
|
16
26
|
|
|
17
|
-
# NAVIGATION
|
|
18
|
-
# using class 'silent' for links when we don't want to trigger onhashchange event
|
|
19
|
-
$(document).on 'click', 'a.silent', (e) -> window._skipHashchange = true
|
|
20
|
-
|
|
21
|
-
window.onhashchange = =>
|
|
22
|
-
if not window._skipHashchange then @_navigate(location.hash)
|
|
23
|
-
window._skipHashchange = false
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
window._skipHashchange = false
|
|
28
|
-
@_navigate(if location.hash != '' then location.hash else '#/' + Object.keys(@modules)[0])
|
|
28
|
+
unsetActiveListItems: ->
|
|
29
|
+
$('.list .items .item.active').removeClass('active')
|
|
29
30
|
|
|
30
|
-
addMenuItem: (moduleName, title) ->
|
|
31
|
-
@$mainMenu.append "<a href='#/#{ moduleName }'>#{ title }</a>"
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@$mainMenu.children(
|
|
32
|
+
_set_active_menu_item: ->
|
|
33
|
+
currentModuleName = window.location.hash.split('/')[1]
|
|
34
|
+
for a in @$mainMenu.children()
|
|
35
|
+
moduleName = $(a).attr('href').split('/')[1]
|
|
36
|
+
if currentModuleName == moduleName
|
|
37
|
+
return $(a).addClass('active')
|
|
36
38
|
|
|
37
|
-
unselectMenuItem: ->
|
|
38
|
-
@$mainMenu.children().removeClass 'active'
|
|
39
39
|
|
|
40
40
|
# TODO: this piece of navigation code isn't clear, need to refactor to make
|
|
41
41
|
# it more readable
|
|
42
|
-
_navigate: (path) ->
|
|
43
|
-
# #/<module>[/<list>][/new]OR[/view/<objectId>]
|
|
42
|
+
_navigate: (path) -> #/<module>[/<list>][/new]OR[/view/<objectId>]
|
|
44
43
|
crumbs = path.split('/')
|
|
45
44
|
|
|
46
|
-
@unselectMenuItem()
|
|
47
|
-
|
|
48
45
|
# if module changed, hide previous module
|
|
49
46
|
if @module != @modules[crumbs[1]]
|
|
50
47
|
@module?.hide((path == '#/')) # NOTE: animate only for root path
|
|
@@ -60,29 +57,83 @@ class @Chr
|
|
|
60
57
|
if crumbs.length > 0
|
|
61
58
|
for crumb in crumbs
|
|
62
59
|
if crumb == 'new'
|
|
63
|
-
# TODO: reset list data
|
|
60
|
+
# TODO: reset list data — why?
|
|
64
61
|
return @module.showView(null, config, 'New')
|
|
65
62
|
|
|
66
63
|
if crumb == 'view'
|
|
67
64
|
objectId = _last(crumbs)
|
|
68
|
-
return @module.
|
|
65
|
+
return @module.showViewByObjectId(objectId, config)
|
|
69
66
|
|
|
70
67
|
config = config.items[crumb]
|
|
71
68
|
|
|
72
69
|
if config.objectStore
|
|
73
|
-
|
|
74
|
-
object = $.extend({ _id: crumb }, config.objectStore.get())
|
|
75
|
-
return @module.showView(object, config, crumb.titleize())
|
|
70
|
+
return @module.showViewByObjectId('', config, crumb.titleize())
|
|
76
71
|
|
|
77
72
|
else
|
|
78
73
|
@module.showNestedList(crumb)
|
|
79
74
|
else
|
|
80
|
-
#
|
|
75
|
+
# show module root list for the case when same module picked
|
|
81
76
|
@module.destroyView()
|
|
82
77
|
while @module.activeList != @module.rootList
|
|
83
78
|
@module.hideActiveList(false)
|
|
84
79
|
|
|
85
80
|
|
|
81
|
+
start: (@config) ->
|
|
82
|
+
@$el =$ (@config.selector ? 'body')
|
|
83
|
+
@$navBar =$ "<nav class='sidebar'>"
|
|
84
|
+
@$mainMenu =$ "<div class='menu'>"
|
|
85
|
+
|
|
86
|
+
@$navBar.append @$mainMenu
|
|
87
|
+
@$el.append @$navBar
|
|
88
|
+
|
|
89
|
+
@modules[name] = new Module(this, name, config) for name, config of @config.modules
|
|
90
|
+
|
|
91
|
+
$(this).on 'hashchange', => @_set_active_menu_item()
|
|
92
|
+
|
|
93
|
+
window.onhashchange = =>
|
|
94
|
+
@_unset_active_menu_items()
|
|
95
|
+
@unsetActiveListItems()
|
|
96
|
+
|
|
97
|
+
# this workaround allows to skip chr _navigate method
|
|
98
|
+
# for silent hashchanges, e.g close view event
|
|
99
|
+
if not window._skipHashchange then @_navigate(location.hash)
|
|
100
|
+
window._skipHashchange = false
|
|
101
|
+
|
|
102
|
+
# triggers hashchange event which is used for navigation
|
|
103
|
+
# related code, e.g. list active item selection
|
|
104
|
+
$(this).trigger 'hashchange'
|
|
105
|
+
|
|
106
|
+
# use class 'silent' for <a> when need to skip onhashchange event
|
|
107
|
+
$(document).on 'click', 'a.silent', (e) -> window._skipHashchange = true
|
|
108
|
+
|
|
109
|
+
# if not mobile navigate on first page load or page refresh
|
|
110
|
+
window._skipHashchange = false
|
|
111
|
+
|
|
112
|
+
# if hash is not empty go to hash path module
|
|
113
|
+
if location.hash != ''
|
|
114
|
+
@_navigate(location.hash)
|
|
115
|
+
$(this).trigger 'hashchange'
|
|
116
|
+
else if ! _isMobile()
|
|
117
|
+
# if on desktop/tablet while hash is empty go to first module in the list
|
|
118
|
+
location.hash = '#/' + Object.keys(@modules)[0]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
addMenuItem: (moduleName, title) ->
|
|
122
|
+
@$mainMenu.append "<a href='#/#{ moduleName }'>#{ title }</a>"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
showAlert: (message) ->
|
|
126
|
+
console.log 'Alert: ' + message
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
showError: (message) ->
|
|
130
|
+
console.log 'Error: ' + message
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# -----------------------------------------------------------------------------
|
|
134
|
+
# INIT
|
|
135
|
+
# -----------------------------------------------------------------------------
|
|
136
|
+
window.chr = new Chr()
|
|
86
137
|
|
|
87
138
|
|
|
88
139
|
|
|
@@ -1,12 +1,53 @@
|
|
|
1
1
|
# -----------------------------------------------------------------------------
|
|
2
|
-
#
|
|
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
|
+
|
|
9
|
+
# -----------------------------------------------------------------------------
|
|
10
|
+
# LIST ITEM
|
|
11
|
+
#
|
|
12
|
+
# public methods:
|
|
13
|
+
# render()
|
|
14
|
+
# destroy()
|
|
15
|
+
# position()
|
|
16
|
+
#
|
|
3
17
|
# -----------------------------------------------------------------------------
|
|
4
18
|
class @Item
|
|
5
|
-
|
|
6
|
-
|
|
19
|
+
constructor: (@module, @path, @object, @config) ->
|
|
20
|
+
@$el =$ """<a class='item' href='#{ @path }' data-id='#{ @object._id }' data-title=''></a>"""
|
|
21
|
+
@$el.on 'click', (e) => @_on_click(e)
|
|
22
|
+
@render()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_on_click: (e) ->
|
|
26
|
+
if @.$el.hasClass('active') then e.preventDefault() ; return
|
|
27
|
+
|
|
28
|
+
window._skipHashchange = true
|
|
29
|
+
location.hash = $(e.currentTarget).attr('href')
|
|
30
|
+
|
|
31
|
+
title = $(e.currentTarget).attr('data-title')
|
|
32
|
+
id = $(e.currentTarget).attr('data-id')
|
|
33
|
+
crumbs = location.href.split('/')
|
|
34
|
+
|
|
35
|
+
# view for a arrayStore item
|
|
36
|
+
if crumbs[crumbs.length - 2] == 'view'
|
|
37
|
+
return @module.showViewByObjectId(id, @config, title, true)
|
|
38
|
+
|
|
39
|
+
if @config.objectStore
|
|
40
|
+
return @module.showViewByObjectId('', @config, title, true)
|
|
41
|
+
|
|
42
|
+
@module.showNestedList(_last(crumbs), true)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_is_folder: ->
|
|
46
|
+
# update this logic as it's not reliable
|
|
7
47
|
if @object._title then true else false
|
|
8
48
|
|
|
9
|
-
|
|
49
|
+
|
|
50
|
+
_render_title: ->
|
|
10
51
|
title = @object._title # nested list title predefined in config (or slug based)
|
|
11
52
|
title ?= @object[@config.itemTitleField] # based on config
|
|
12
53
|
title ?= _firstNonEmptyValue(@object) # auto-generated: first non empty value
|
|
@@ -17,7 +58,8 @@ class @Item
|
|
|
17
58
|
@$el.append(@$title)
|
|
18
59
|
@$el.attr 'data-title', title
|
|
19
60
|
|
|
20
|
-
|
|
61
|
+
|
|
62
|
+
_render_subtitle: ->
|
|
21
63
|
if @config.itemSubtitleField
|
|
22
64
|
subtitle = @object[@config.itemSubtitleField]
|
|
23
65
|
if subtitle != ''
|
|
@@ -25,56 +67,37 @@ class @Item
|
|
|
25
67
|
@$el.append(@$subtitle)
|
|
26
68
|
@$el.addClass 'has-subtitle'
|
|
27
69
|
|
|
28
|
-
|
|
70
|
+
|
|
71
|
+
_render_thumbnail: ->
|
|
29
72
|
if @config.itemThumbnail
|
|
30
73
|
imageUrl = @config.itemThumbnail(@object)
|
|
31
|
-
|
|
74
|
+
# carrierwave fix, check if still required
|
|
75
|
+
if imageUrl != '' and not imageUrl.endsWith('_old_')
|
|
32
76
|
@$thumbnail =$ "<div class='item-thumbnail'><img src='#{imageUrl}' /></div>"
|
|
33
77
|
@$el.append(@$thumbnail)
|
|
34
78
|
@$el.addClass 'has-thumbnail'
|
|
35
79
|
|
|
80
|
+
|
|
36
81
|
render: ->
|
|
37
82
|
@$el.html('').removeClass('item-folder has-subtitle has-thumbnail')
|
|
38
|
-
@
|
|
83
|
+
@_render_title()
|
|
39
84
|
|
|
40
|
-
if @
|
|
85
|
+
if @_is_folder()
|
|
41
86
|
@$el.addClass('item-folder')
|
|
42
87
|
@$el.append $("<div class='icon-folder'></div>")
|
|
43
88
|
else
|
|
44
|
-
@
|
|
45
|
-
@
|
|
89
|
+
@_render_subtitle()
|
|
90
|
+
@_render_thumbnail()
|
|
46
91
|
|
|
47
92
|
if @config.arrayStore and @config.arrayStore.reorderable
|
|
48
93
|
@$el.addClass('reorderable')
|
|
49
94
|
@$el.append $("<div class='icon-reorder'></div>")
|
|
50
95
|
|
|
51
|
-
constructor: (@module, @path, @object, @config) ->
|
|
52
|
-
@$el =$ """<a class='item silent' href='#{ @path }' data-id='#{ @object._id }' data-title=''></a>"""
|
|
53
|
-
@$el.on 'click', (e) => @onClick(e)
|
|
54
|
-
@render()
|
|
55
|
-
|
|
56
|
-
onClick: (e) ->
|
|
57
|
-
window._skipHashchange = true
|
|
58
|
-
location.hash = $(e.currentTarget).attr('href')
|
|
59
|
-
|
|
60
|
-
title = $(e.currentTarget).attr('data-title')
|
|
61
|
-
id = $(e.currentTarget).attr('data-id')
|
|
62
|
-
crumbs = location.href.split('/')
|
|
63
|
-
|
|
64
|
-
if @config.arrayStore and crumbs[crumbs.length - 2] == 'view'
|
|
65
|
-
object = @config.arrayStore.get(id)
|
|
66
|
-
|
|
67
|
-
if @config.objectStore
|
|
68
|
-
object = @config.objectStore.get()
|
|
69
|
-
|
|
70
|
-
if object
|
|
71
|
-
return @module.showView(object, @config, title, true)
|
|
72
|
-
|
|
73
|
-
@module.showNestedList(_last(crumbs), true)
|
|
74
96
|
|
|
75
97
|
destroy: ->
|
|
76
98
|
@$el.remove()
|
|
77
99
|
|
|
100
|
+
|
|
78
101
|
position: ->
|
|
79
102
|
positionFieldName = @config.arrayStore.sortBy
|
|
80
103
|
parseFloat(@object[positionFieldName])
|
|
@@ -82,4 +105,3 @@ class @Item
|
|
|
82
105
|
|
|
83
106
|
|
|
84
107
|
|
|
85
|
-
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# -----------------------------------------------------------------------------
|
|
2
2
|
# LIST SCROLL
|
|
3
|
+
# todo:
|
|
4
|
+
# - trigger onScroll event only when scrolling down
|
|
3
5
|
# -----------------------------------------------------------------------------
|
|
4
|
-
@
|
|
6
|
+
@_listBindPagination = (listEl) ->
|
|
5
7
|
$container = listEl.$el
|
|
6
8
|
$list = listEl.$items
|
|
7
9
|
arrayStore = listEl.config.arrayStore
|
|
8
10
|
|
|
9
11
|
$list.scroll (e) =>
|
|
10
|
-
if
|
|
12
|
+
if ! arrayStore.dataFetchLock
|
|
11
13
|
$listChildren = $list.children()
|
|
12
14
|
listChildrenCount = $listChildren.length
|
|
13
15
|
listFirstChildHeight = $listChildren.first().outerHeight()
|
|
@@ -15,7 +17,8 @@
|
|
|
15
17
|
viewHeight = $container.height()
|
|
16
18
|
|
|
17
19
|
if listHeight < (viewHeight + e.target.scrollTop + 100)
|
|
18
|
-
listEl.
|
|
20
|
+
listEl._show_spinner()
|
|
21
|
+
arrayStore.load()
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
config = arrayStore.reorderable
|
|
14
14
|
|
|
15
|
-
#
|
|
15
|
+
# this is optimistic scenario when assumes that all positions are different
|
|
16
16
|
_getObjectNewPosition = (el) ->
|
|
17
17
|
$el =$ el
|
|
18
18
|
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
), false
|
|
48
48
|
|
|
49
49
|
list.addEventListener 'slip:reorder', ((e) =>
|
|
50
|
-
#
|
|
50
|
+
# when `e.detail.insertBefore` is null, item put to the end of the list.
|
|
51
51
|
e.target.parentNode.insertBefore(e.target, e.detail.insertBefore)
|
|
52
52
|
|
|
53
53
|
objectPositionValue = _getObjectNewPosition(e.target)
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
value["[#{arrayStore.sortBy}"] = "#{ objectPositionValue }"
|
|
57
57
|
|
|
58
58
|
arrayStore.update objectId, value,
|
|
59
|
-
#
|
|
59
|
+
# error handling
|
|
60
60
|
onSuccess: (object) => ;
|
|
61
61
|
onError: (errors) => ;
|
|
62
62
|
|
|
@@ -5,23 +5,32 @@
|
|
|
5
5
|
$input = listEl.$search
|
|
6
6
|
arrayStore = listEl.config.arrayStore
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
query = $(e.target).val()
|
|
13
|
-
listEl._loading -> arrayStore.search(query)
|
|
8
|
+
runSearch = (input) ->
|
|
9
|
+
query = $(input).val()
|
|
10
|
+
listEl._show_spinner()
|
|
11
|
+
arrayStore.search(query)
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
e.preventDefault()
|
|
13
|
+
showSearch = ->
|
|
17
14
|
listEl.$el.addClass 'list-search'
|
|
18
15
|
$input.find('input').focus()
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
e.preventDefault()
|
|
17
|
+
cancelSearch = ->
|
|
22
18
|
listEl.$el.removeClass 'list-search'
|
|
23
19
|
$input.find('input').val('')
|
|
24
|
-
listEl.
|
|
20
|
+
listEl._show_spinner()
|
|
21
|
+
arrayStore.reset()
|
|
22
|
+
|
|
23
|
+
$input.show()
|
|
24
|
+
|
|
25
|
+
$input.on 'keyup', 'input', (e) =>
|
|
26
|
+
if e.keyCode == 27 # esc
|
|
27
|
+
return cancelSearch()
|
|
28
|
+
|
|
29
|
+
if e.keyCode == 13 # enter
|
|
30
|
+
return runSearch(e.target)
|
|
31
|
+
|
|
32
|
+
$input.on 'click', '.icon', (e) => e.preventDefault() ; showSearch()
|
|
33
|
+
$input.on 'click', '.cancel', (e) => e.preventDefault() ; cancelSearch()
|
|
25
34
|
|
|
26
35
|
|
|
27
36
|
|
|
@@ -1,10 +1,105 @@
|
|
|
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
|
# LIST
|
|
11
|
+
#
|
|
12
|
+
# configuration options:
|
|
13
|
+
# itemClass - item class to be used instead of default one
|
|
14
|
+
# itemTitleField - object attributes name for list item title
|
|
15
|
+
# itemSubtitleField - object attributes name for list item subtitle
|
|
16
|
+
# disableNewItems - do not show new item button in list header
|
|
17
|
+
# disableUpdateItems - do not update list items
|
|
18
|
+
# onListInit - callback on list is initialized
|
|
19
|
+
# onListShow - callback on list is shown
|
|
20
|
+
# objects - objects array to be added to the store on start
|
|
21
|
+
#
|
|
22
|
+
# public methods:
|
|
23
|
+
# hide() - hide list
|
|
24
|
+
# show() - show list
|
|
25
|
+
# updateItems() - update list items (sync through store with backend)
|
|
26
|
+
# isVisible() - check if list is visible
|
|
27
|
+
#
|
|
3
28
|
# -----------------------------------------------------------------------------
|
|
4
29
|
class @List
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
30
|
+
constructor: (@module, @name, @config, @parentList) ->
|
|
31
|
+
@configItemsCount = 0
|
|
32
|
+
@path = @_path()
|
|
33
|
+
@items = {}
|
|
34
|
+
@title = @config.title ? @name.titleize()
|
|
35
|
+
@itemClass = @config.itemClass ? Item
|
|
36
|
+
@showWithParent = false
|
|
37
|
+
if @parentList
|
|
38
|
+
@showWithParent = @parentList.config.showNestedListsAside || false
|
|
39
|
+
|
|
40
|
+
@config.showListWithParent ? false
|
|
41
|
+
|
|
42
|
+
@$el =$ "<div class='list #{ @name }'>"
|
|
43
|
+
@module.$el.append @$el
|
|
44
|
+
|
|
45
|
+
# hide all nested lists
|
|
46
|
+
if @parentList then @$el.hide()
|
|
47
|
+
|
|
48
|
+
# items
|
|
49
|
+
@$items =$ "<div class='items'>"
|
|
50
|
+
@$el.append @$items
|
|
51
|
+
|
|
52
|
+
# header
|
|
53
|
+
@$header =$ "<header></header>"
|
|
54
|
+
@$el.append @$header
|
|
55
|
+
|
|
56
|
+
# back button
|
|
57
|
+
if @parentList
|
|
58
|
+
@$backBtn =$ "<a href='#/#{ @parentList.path }' class='back silent'></a>"
|
|
59
|
+
@$backBtn.on 'click', (e) => @_on_back(e)
|
|
60
|
+
else
|
|
61
|
+
@$backBtn =$ "<a href='#/' class='back'></a>"
|
|
62
|
+
@$header.prepend @$backBtn
|
|
63
|
+
|
|
64
|
+
# spinner & title
|
|
65
|
+
@$header.append "<div class='spinner'></div>"
|
|
66
|
+
@$header.append "<span class='title'>#{ @title }</span>"
|
|
67
|
+
|
|
68
|
+
# new item button
|
|
69
|
+
if not @config.disableNewItems and @config.formSchema
|
|
70
|
+
@$newBtn =$ "<a href='#/#{ @path }/new' class='new silent'></a>"
|
|
71
|
+
@$newBtn.on 'click', (e) => @_on_new(e)
|
|
72
|
+
@$header.append @$newBtn
|
|
73
|
+
|
|
74
|
+
# search
|
|
75
|
+
@$search =$ """<div class='search' style='display: none;'>
|
|
76
|
+
<a href='#' class='icon'></a>
|
|
77
|
+
<input type='text' placeholder='Search...' />
|
|
78
|
+
<a href='#' class='cancel'>Cancel</a>
|
|
79
|
+
</div>"""
|
|
80
|
+
@$header.append @$search
|
|
81
|
+
|
|
82
|
+
if @config.items then @_process_config_items()
|
|
83
|
+
if @config.arrayStore then @_bind_config_array_store()
|
|
84
|
+
if @config.objectStore then @_bind_config_object_store()
|
|
85
|
+
|
|
86
|
+
@_bind_hashchange()
|
|
87
|
+
|
|
88
|
+
@config.onListInit?(@)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
_bind_hashchange: ->
|
|
92
|
+
$(chr).on 'hashchange', => @_set_active_item()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
_set_active_item: ->
|
|
96
|
+
hash = window.location.hash
|
|
97
|
+
if hash.startsWith "#/#{ @module.name }"
|
|
98
|
+
for a in @$items.children()
|
|
99
|
+
itemPath = $(a).attr('href')
|
|
100
|
+
if hash.startsWith(itemPath)
|
|
101
|
+
return $(a).addClass('active')
|
|
102
|
+
|
|
8
103
|
|
|
9
104
|
_path: ->
|
|
10
105
|
crumbs = [] ; l = this
|
|
@@ -12,56 +107,49 @@ class @List
|
|
|
12
107
|
crumbs.push(l.name) ; l = l.parentList
|
|
13
108
|
@module.name + ( if crumbs.length > 0 then '/' + crumbs.reverse().join('/') else '' )
|
|
14
109
|
|
|
15
|
-
_updateItemPosition: (item, position) ->
|
|
16
|
-
position = @configItemsCount + position
|
|
17
|
-
if position == 0
|
|
18
|
-
@$items.prepend(item.$el)
|
|
19
|
-
else
|
|
20
|
-
@$items.append(item.$el.hide())
|
|
21
|
-
$(@$items.children()[position - 1]).after(item.$el.show())
|
|
22
|
-
|
|
23
|
-
_addItem: (path, object, position, config) ->
|
|
24
|
-
item = new Item(@module, path, object, config)
|
|
25
|
-
@items[object._id] = item
|
|
26
|
-
@_updateItemPosition(item, position)
|
|
27
110
|
|
|
28
|
-
|
|
111
|
+
_process_config_items: ->
|
|
29
112
|
for slug, config of @config.items
|
|
30
113
|
object = { _id: slug, _title: config.title ? slug.titleize() }
|
|
31
114
|
|
|
32
|
-
if config.objectStore
|
|
33
|
-
|
|
115
|
+
#if config.objectStore
|
|
116
|
+
# $.extend(object, config.objectStore.get())
|
|
34
117
|
|
|
35
118
|
if config.items or config.arrayStore
|
|
36
119
|
@module.addNestedList(slug, config, this)
|
|
37
120
|
|
|
38
|
-
@
|
|
121
|
+
@_add_item("#/#{ @path }/#{ slug }", object, 0, config)
|
|
39
122
|
@configItemsCount += 1
|
|
40
123
|
|
|
41
|
-
_bindConfigObjectStore: ->
|
|
42
124
|
|
|
43
|
-
|
|
125
|
+
_bind_config_object_store: ->
|
|
44
126
|
|
|
45
|
-
|
|
127
|
+
|
|
128
|
+
_bind_config_array_store: ->
|
|
129
|
+
# item added
|
|
46
130
|
@config.arrayStore.on 'object_added', (e, data) =>
|
|
47
|
-
@
|
|
48
|
-
|
|
131
|
+
@_add_item("#/#{ @path }/view/#{ data.object._id }", data.object, data.position, @config)
|
|
132
|
+
|
|
133
|
+
if @config.objects
|
|
134
|
+
@config.arrayStore.addObjects(@config.objects)
|
|
49
135
|
|
|
136
|
+
# item updated
|
|
50
137
|
@config.arrayStore.on 'object_changed', (e, data) =>
|
|
51
138
|
item = @items[data.object._id]
|
|
52
|
-
item.render()
|
|
53
|
-
@_updateItemPosition(item, data.position)
|
|
54
|
-
data.callback?(data.object)
|
|
139
|
+
if item then item.render() ; @_update_item_position(item, data.position)
|
|
55
140
|
|
|
141
|
+
# item removed
|
|
56
142
|
@config.arrayStore.on 'object_removed', (e, data) =>
|
|
57
|
-
@items[data.object_id]
|
|
58
|
-
delete @items[data.object_id]
|
|
143
|
+
item = @items[data.object_id]
|
|
144
|
+
if item then item.destroy() ; delete @items[data.object_id]
|
|
59
145
|
|
|
146
|
+
# items loaded
|
|
60
147
|
@config.arrayStore.on 'objects_added', (e, data) =>
|
|
61
|
-
|
|
148
|
+
@_hide_spinner()
|
|
149
|
+
@_set_active_item()
|
|
62
150
|
|
|
63
151
|
if @config.arrayStore.pagination
|
|
64
|
-
|
|
152
|
+
_listBindPagination(this)
|
|
65
153
|
|
|
66
154
|
if @config.arrayStore.searchable
|
|
67
155
|
_listBindSearch(this)
|
|
@@ -69,88 +157,74 @@ class @List
|
|
|
69
157
|
if @config.arrayStore.reorderable
|
|
70
158
|
_listBindReorder(this)
|
|
71
159
|
|
|
72
|
-
constructor: (@module, @name, @config, @parentList) ->
|
|
73
|
-
@configItemsCount = 0
|
|
74
|
-
@path = @_path()
|
|
75
|
-
@items = {}
|
|
76
|
-
@title = @config.title ? @name.titleize()
|
|
77
160
|
|
|
78
|
-
|
|
79
|
-
@module
|
|
161
|
+
_add_item: (path, object, position, config) ->
|
|
162
|
+
item = new @itemClass(@module, path, object, config)
|
|
163
|
+
@items[object._id] = item
|
|
164
|
+
@_update_item_position(item, position)
|
|
80
165
|
|
|
81
|
-
if @parentList then @$el.hide() # hide all nested lists
|
|
82
166
|
|
|
83
|
-
|
|
84
|
-
|
|
167
|
+
_update_item_position: (item, position) ->
|
|
168
|
+
position = @configItemsCount + position
|
|
169
|
+
if position == 0
|
|
170
|
+
@$items.prepend(item.$el)
|
|
171
|
+
else
|
|
172
|
+
@$items.append(item.$el.hide())
|
|
173
|
+
$(@$items.children()[position - 1]).after(item.$el.show())
|
|
85
174
|
|
|
86
|
-
@$header =$ "<header></header>"
|
|
87
175
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@parentListPath = @parentList.path
|
|
91
|
-
@$backBtn =$ "<a href='#/#{ @parentListPath }' class='back silent'></a>"
|
|
92
|
-
@$backBtn.on 'click', (e) => @onBack(e)
|
|
93
|
-
@$header.prepend @$backBtn
|
|
94
|
-
else
|
|
95
|
-
@$backBtn =$ "<a href='#/' class='back'></a>"
|
|
96
|
-
@$header.prepend @$backBtn
|
|
176
|
+
_show_spinner: ->
|
|
177
|
+
@$el.addClass 'show-spinner'
|
|
97
178
|
|
|
98
179
|
|
|
99
|
-
|
|
180
|
+
_hide_spinner: ->
|
|
181
|
+
@$el.removeClass 'show-spinner'
|
|
100
182
|
|
|
101
|
-
if not @config.disableNewItems and @config.formSchema
|
|
102
|
-
@$newBtn =$ "<a href='#/#{ @path }/new' class='new silent'></a>"
|
|
103
|
-
@$newBtn.on 'click', (e) => @onNew(e)
|
|
104
|
-
@$header.append @$newBtn
|
|
105
183
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<a href='#' class='cancel'>Cancel</a>
|
|
110
|
-
</div>"""
|
|
111
|
-
@$header.append @$search
|
|
184
|
+
_on_back: (e) ->
|
|
185
|
+
@module.chr.unsetActiveListItems()
|
|
186
|
+
@module.destroyView()
|
|
112
187
|
|
|
113
|
-
|
|
188
|
+
if @showWithParent
|
|
189
|
+
@hide(true)
|
|
190
|
+
else
|
|
191
|
+
@module.hideActiveList(true)
|
|
114
192
|
|
|
115
|
-
if @config.items then @_processConfigItems()
|
|
116
|
-
if @config.arrayStore then @_bindConfigArrayStore()
|
|
117
|
-
if @config.objectStore then @_bindConfigObjectStore()
|
|
118
193
|
|
|
119
|
-
|
|
194
|
+
_on_new: (e) ->
|
|
195
|
+
window._skipHashchange = true
|
|
196
|
+
location.hash = $(e.currentTarget).attr('href')
|
|
197
|
+
@module.showView(null, @config, 'New', true)
|
|
120
198
|
|
|
121
|
-
selectItem: (href) ->
|
|
122
|
-
@$items.children("a[href='#{ href }']").addClass 'active'
|
|
123
199
|
|
|
124
|
-
|
|
125
|
-
@$
|
|
200
|
+
hide: (animate) ->
|
|
201
|
+
if animate then @$el.fadeOut() else @$el.hide()
|
|
126
202
|
|
|
127
|
-
hide: ->
|
|
128
|
-
@unselectItems()
|
|
129
|
-
@$el.hide()
|
|
130
203
|
|
|
131
|
-
show: (animate=false) ->
|
|
204
|
+
show: (animate=false, callback) ->
|
|
205
|
+
onShow = =>
|
|
206
|
+
@$items.scrollTop(0)
|
|
207
|
+
@config.onListShow?(@)
|
|
208
|
+
callback?()
|
|
209
|
+
|
|
132
210
|
if animate
|
|
133
|
-
|
|
211
|
+
# z-index workaround to remove blink effect
|
|
212
|
+
@$el.css({ 'z-index': 1, 'box-shadow': 'none' })
|
|
213
|
+
@$el.fadeIn $.fx.speeds._default, => @$el.css({ 'z-index': '', 'box-shadow': '' }) ; onShow()
|
|
134
214
|
else
|
|
135
|
-
@$el.show()
|
|
215
|
+
@$el.show() ; onShow()
|
|
136
216
|
|
|
137
|
-
onBack: (e) ->
|
|
138
|
-
@unselectItems()
|
|
139
|
-
@module.hideActiveList(true)
|
|
140
|
-
@module.destroyView()
|
|
141
217
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
location.hash = $(e.currentTarget).attr('href')
|
|
145
|
-
@module.showView(null, @config, 'New', true)
|
|
146
|
-
|
|
147
|
-
updateItems: (callback) ->
|
|
148
|
-
if not @config.disableReset
|
|
218
|
+
updateItems: ->
|
|
219
|
+
if not @config.disableUpdateItems
|
|
149
220
|
if @config.arrayStore
|
|
150
|
-
@
|
|
221
|
+
@_show_spinner()
|
|
222
|
+
@config.arrayStore.reset()
|
|
223
|
+
|
|
151
224
|
|
|
152
225
|
isVisible: ->
|
|
153
226
|
@$el.is(':visible')
|
|
154
227
|
|
|
155
228
|
|
|
156
229
|
|
|
230
|
+
|