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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gruntfile.coffee +6 -3
  3. data/LICENSE.md +1 -1
  4. data/README.md +286 -7
  5. data/app/assets/javascripts/chr-dist.js +1031 -580
  6. data/app/assets/javascripts/chr.coffee +7 -4
  7. data/app/assets/javascripts/chr/core/chr.coffee +88 -37
  8. data/app/assets/javascripts/chr/core/item.coffee +57 -35
  9. data/app/assets/javascripts/chr/core/{list-scroll.coffee → list-pagination.coffee} +6 -3
  10. data/app/assets/javascripts/chr/core/list-reorder.coffee +3 -3
  11. data/app/assets/javascripts/chr/core/list-search.coffee +20 -11
  12. data/app/assets/javascripts/chr/core/list.coffee +163 -89
  13. data/app/assets/javascripts/chr/core/module.coffee +75 -35
  14. data/app/assets/javascripts/chr/core/view.coffee +117 -61
  15. data/app/assets/javascripts/chr/store/{store.coffee → _array-store.coffee} +53 -106
  16. data/app/assets/javascripts/chr/store/_object-store.coffee +28 -0
  17. data/app/assets/javascripts/chr/store/mongosteen-array-store.coffee +199 -0
  18. data/app/assets/javascripts/chr/store/mongosteen-object-store.coffee +52 -0
  19. data/app/assets/javascripts/chr/store/rest-array-store.coffee +142 -0
  20. data/app/assets/javascripts/chr/store/rest-object-store.coffee +79 -0
  21. data/app/assets/stylesheets/core/_list.scss +20 -25
  22. data/app/assets/stylesheets/core/_main.scss +3 -4
  23. data/app/assets/stylesheets/core/_mixins.scss +33 -2
  24. data/app/assets/stylesheets/core/_responsive.scss +30 -9
  25. data/app/assets/stylesheets/form/_input_checkbox.scss +18 -14
  26. data/bower.json +3 -2
  27. data/chr.gemspec +1 -1
  28. data/docs/assets.md +0 -0
  29. data/docs/basics.md +0 -0
  30. data/docs/bootstrap-data.md +0 -0
  31. data/docs/custom-inputs.md +0 -0
  32. data/docs/form.md +0 -0
  33. data/docs/internals.md +0 -0
  34. data/docs/nested-forms.md +0 -0
  35. data/docs/redactor-js.md +0 -0
  36. data/docs/scopes.md +0 -0
  37. data/lib/chr/version.rb +1 -1
  38. data/package.json +1 -1
  39. metadata +19 -7
  40. data/app/assets/javascripts/chr/store/store-mongosteen.coffee +0 -111
  41. 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-scroll
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-rest
27
- #= require ./chr/store/store-mongosteen
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
- # Character
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: (@config) ->
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
- @$navBar.append @$mainMenu
13
- @$el.append @$navBar
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
- # if not mobile navigate on first page load or page refresh
26
- if not _isMobile()
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
- selectMenuItem: (href) ->
34
- @$mainMenu.children().removeClass 'active'
35
- @$mainMenu.children("a[href='#/#{ href }']").addClass 'active'
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.showViewWhenObjectsAreReady(objectId, config)
65
+ return @module.showViewByObjectId(objectId, config)
69
66
 
70
67
  config = config.items[crumb]
71
68
 
72
69
  if config.objectStore
73
- # TODO: check if object is loaded and if it's not load it first
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
- # NOTE: show module root list for the case when same module picked
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
- # ITEM
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
- _isFolder: ->
6
- # TODO: update this logic as it's not reliable
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
- _renderTitle: ->
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
- _renderSubtitle: ->
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
- _renderThumbnail: ->
70
+
71
+ _render_thumbnail: ->
29
72
  if @config.itemThumbnail
30
73
  imageUrl = @config.itemThumbnail(@object)
31
- if imageUrl != '' and not imageUrl.endsWith('_old_') # NOTE: carrierwave fix
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
- @_renderTitle()
83
+ @_render_title()
39
84
 
40
- if @_isFolder()
85
+ if @_is_folder()
41
86
  @$el.addClass('item-folder')
42
87
  @$el.append $("<div class='icon-folder'></div>")
43
88
  else
44
- @_renderSubtitle()
45
- @_renderThumbnail()
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
- @_listBindScroll = (listEl) ->
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 not arrayStore.dataFetchLock
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._loading -> arrayStore.load()
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
- # NOTE: this is optimistic scenario when assumes that all positions are different
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
- # NOTE: when `e.detail.insertBefore` is null, item put to the end of the list.
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
- # NOTE: error handling
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
- $input.show()
9
-
10
- $input.on 'keydown', 'input', (e) =>
11
- if e.keyCode == 13
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
- $input.on 'click', '.icon', (e) =>
16
- e.preventDefault()
13
+ showSearch = ->
17
14
  listEl.$el.addClass 'list-search'
18
15
  $input.find('input').focus()
19
16
 
20
- $input.on 'click', '.cancel', (e) =>
21
- e.preventDefault()
17
+ cancelSearch = ->
22
18
  listEl.$el.removeClass 'list-search'
23
19
  $input.find('input').val('')
24
- listEl._loading -> arrayStore.reset()
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
- _loading: (callback) ->
6
- @$el.addClass 'list-loading'
7
- callback()
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
- _processConfigItems: ->
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
- $.extend(object, config.objectStore.get())
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
- @_addItem("#/#{ @path }/#{ slug }", object, 0, config)
121
+ @_add_item("#/#{ @path }/#{ slug }", object, 0, config)
39
122
  @configItemsCount += 1
40
123
 
41
- _bindConfigObjectStore: ->
42
124
 
43
- _bindConfigArrayStore: ->
125
+ _bind_config_object_store: ->
44
126
 
45
- # NOTE: starts data fetch
127
+
128
+ _bind_config_array_store: ->
129
+ # item added
46
130
  @config.arrayStore.on 'object_added', (e, data) =>
47
- @_addItem("#/#{ @path }/view/#{ data.object._id }", data.object, data.position, @config)
48
- data.callback?(data.object)
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].destroy()
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
- @$el.removeClass 'list-loading'
148
+ @_hide_spinner()
149
+ @_set_active_item()
62
150
 
63
151
  if @config.arrayStore.pagination
64
- _listBindScroll(this)
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
- @$el =$ "<div class='list #{ @name }'>"
79
- @module.$el.append @$el
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
- @$items =$ "<div class='items'>"
84
- @$el.append @$items
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
- if @parentList
89
- # NOTE: show back button for nested list
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
- @$header.append "<span class='title'>#{ @title }</span>"
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
- @$search =$ """<div class='search' style='display: none;'>
107
- <a href='#' class='icon'></a>
108
- <input type='text' placeholder='Search...' />
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
- @$el.append @$header
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
- @config.onListInit?(@)
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
- unselectItems: ->
125
- @$items.children().removeClass 'active'
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
- @$el.fadeIn()
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
- onNew: (e) ->
143
- window._skipHashchange = true
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
- @_loading => @config.arrayStore.reset(callback)
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
+