chr 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,28 @@
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
+
9
+ # -----------------------------------------------------------------------------
10
+ # OBJECT STORE
11
+ # -----------------------------------------------------------------------------
12
+ class @ObjectStore
13
+ constructor: (@config={}) ->
14
+ @_initialize_database()
15
+
16
+ _initialize_database: ->
17
+ @_data = @config.data
18
+
19
+ loadObject: ->
20
+ @_data
21
+
22
+ update: (id, value, callback) ->
23
+ $.extend(@_data, value)
24
+ callback?(@_data)
25
+
26
+
27
+
28
+
@@ -0,0 +1,199 @@
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
+
9
+ # -----------------------------------------------------------------------------
10
+ # MONGOSTEEN (RAILS) ARRAY/COLLECTION STORE IMPLEMENTATION
11
+ # this store implementation talks to Mongosteen powered Rails api, supports
12
+ # features:
13
+ #
14
+ # - pagination
15
+ # `sortBy` & `sortReverse` options should be set same as on
16
+ # backend model with `default_scope` method (default), e.g:
17
+ # - frontend: `{ sortBy: 'created_at', sortReverse: true }`
18
+ # - backend: `default_scope -> { desc(:created_at) }`
19
+ #
20
+ # - search
21
+ # backend model configuration required, e.g: `search_in :title`
22
+ #
23
+ # configuration options:
24
+ # @config.pagination - enable pagination, default `true`
25
+ # @config.searchable - enable search, default `false`
26
+ #
27
+ # -----------------------------------------------------------------------------
28
+ class @MongosteenArrayStore extends RestArrayStore
29
+ # initial store configuration
30
+ _initialize_database: ->
31
+ @dataFetchLock = false
32
+ @ajaxConfig =
33
+ processData: false
34
+ contentType: false
35
+
36
+ @searchable = @config.searchable ? false
37
+ @searchQuery = ''
38
+
39
+ @pagination = @config.pagination ? true
40
+ @nextPage = 1
41
+ @objectsPerPage = _itemsPerPageRequest ? 20
42
+
43
+ if @pagination
44
+ @_bind_pagination_sync()
45
+
46
+
47
+ # ---------------------------------------------------------
48
+ # workarounds to have consistency between arrayStore and
49
+ # database while loading next page
50
+ # ---------------------------------------------------------
51
+ _bind_pagination_sync: ->
52
+ @lastPageLoaded = false
53
+
54
+ # when object's added to the end of the list & not on the last page,
55
+ # we don't know it's position on the backend, so remove it from store
56
+ $(this).on 'object_added', (e, data) =>
57
+ if ! @lastPageLoaded
58
+ new_object = data.object
59
+ new_object_position = data.position
60
+
61
+ # check if object added to the end of the list
62
+ if new_object_position >= @objectsNumberForLoadedPages
63
+ e.stopImmediatePropagation()
64
+
65
+ @_remove_data_object(new_object._id)
66
+
67
+ # when object's added to the end of the list & not on the last page,
68
+ # we don't know it's position on the backend, so remove it from store
69
+ $(this).on 'object_changed', (e, data) =>
70
+ if ! @lastPageLoaded
71
+ new_object = data.object
72
+ new_object_position = data.position
73
+
74
+ # check if object added to the end of the list
75
+ if new_object_position >= @objectsNumberForLoadedPages - 1
76
+ e.stopImmediatePropagation()
77
+
78
+ @_remove_data_object(new_object._id)
79
+
80
+ # load current page again after item delete to sync, last item on the page
81
+ $(this).on 'object_removed', (e, data) =>
82
+ if ! @lastPageLoaded
83
+ @_reload_current_page()
84
+
85
+
86
+ _reload_current_page: ->
87
+ @nextPage -= 1 ; @load()
88
+
89
+
90
+ _udpate_next_page: (data) ->
91
+ if @pagination
92
+ if data.length > 0
93
+ @lastPageLoaded = true
94
+
95
+ if data.length == @objectsPerPage
96
+ @nextPage += 1
97
+ @lastPageLoaded = false
98
+
99
+ else
100
+ @lastPageLoaded = true
101
+
102
+ @objectsNumberForLoadedPages = (@nextPage - 1) * @objectsPerPage
103
+
104
+
105
+ # generate resource api url
106
+ _resource_url: (type, id) ->
107
+ objectPath = if id then "/#{ id }" else ''
108
+ url = "#{ @config.path }#{ objectPath }.json"
109
+
110
+ if @config.urlParams
111
+ extraParamsString = $.param(@config.urlParams)
112
+ url = "#{ url }?#{ extraParamsString }"
113
+
114
+ return url
115
+
116
+
117
+ # get form data object from serialized form object,
118
+ # it uses special format for object names for support of:
119
+ # files, lists, nested objects
120
+ _parse_form_object: (serializedFormObject) ->
121
+ formDataObject = new FormData()
122
+
123
+ for attr_name, attr_value of serializedFormObject
124
+
125
+ # special case for LIST inputs, values separated with comma
126
+ if attr_name.indexOf('[__LIST__') > -1
127
+ attr_name = attr_name.replace('__LIST__', '')
128
+ values = attr_value.split(',')
129
+
130
+ for value in values
131
+ formDataObject.append("#{ @config.resource }#{ attr_name }[]", value)
132
+
133
+ else
134
+ # special case for FILE inputs
135
+ if attr_name.startsWith('__FILE__')
136
+ attr_name = attr_name.replace('__FILE__', '')
137
+
138
+ formDataObject.append("#{ @config.resource }#{ attr_name }", attr_value)
139
+
140
+ return formDataObject
141
+
142
+
143
+ # load results for search query
144
+ search: (@searchQuery) ->
145
+ @nextPage = 1
146
+ @_reset_data()
147
+ @load()
148
+
149
+
150
+ # load next page objects from database, when finished
151
+ # trigger 'objects_added' event
152
+ load: (callbacks={}) ->
153
+ callbacks.onSuccess ?= $.noop
154
+ callbacks.onError ?= $.noop
155
+
156
+ params = {}
157
+
158
+ if @pagination
159
+ params.page = @nextPage
160
+ params.perPage = @objectsPerPage
161
+
162
+ if @searchable && @searchQuery.length > 0
163
+ params.search = @searchQuery
164
+
165
+ params = $.param(params)
166
+
167
+ @_ajax 'GET', null, params, ((data) =>
168
+ @_udpate_next_page(data)
169
+ @_add_data_object(o) for o in data
170
+
171
+ callbacks.onSuccess(data)
172
+
173
+ $(this).trigger('objects_added', { objects: data })
174
+ ), callbacks.onError
175
+
176
+
177
+ # reset data and load first page
178
+ reset: ->
179
+ @searchQuery = ''
180
+ @nextPage = 1
181
+ params = {}
182
+
183
+ if @pagination
184
+ @lastPageLoaded = false
185
+ params.page = @nextPage
186
+ params.perPage = @objectsPerPage
187
+
188
+ params = $.param(params)
189
+
190
+ @_ajax 'GET', null, params, ((data) =>
191
+ @_udpate_next_page(data)
192
+ @_sync_with_data_objects(data)
193
+
194
+ $(this).trigger('objects_added', { objects: data })
195
+ ), -> chr.showError('Error while loading data.')
196
+
197
+
198
+
199
+
@@ -0,0 +1,52 @@
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
+
9
+ # -----------------------------------------------------------------------------
10
+ # MONGOSTEEN (RAILS) OBJECT STORE IMPLEMENTATION
11
+ # -----------------------------------------------------------------------------
12
+ class @MongosteenObjectStore extends RestObjectStore
13
+ # initial store configuration
14
+ _initialize_database: ->
15
+ @dataFetchLock = false
16
+ @ajaxConfig =
17
+ processData: false
18
+ contentType: false
19
+
20
+
21
+ # generate rest url for resource
22
+ _resource_url: -> "#{ @config.path }.json"
23
+
24
+
25
+ # get form data object from serialized form object,
26
+ # it uses special format for object names for support of:
27
+ # files, lists, nested objects
28
+ _parse_form_object: (serializedFormObject) ->
29
+ formDataObject = new FormData()
30
+
31
+ for attr_name, attr_value of serializedFormObject
32
+
33
+ # special case for LIST inputs, values separated with comma
34
+ if attr_name.indexOf('[__LIST__') > -1
35
+ attr_name = attr_name.replace('__LIST__', '')
36
+ values = attr_value.split(',')
37
+
38
+ for value in values
39
+ formDataObject.append("#{ @config.resource }#{ attr_name }[]", value)
40
+
41
+ else
42
+ # special case for FILE inputs
43
+ if attr_name.startsWith('__FILE__')
44
+ attr_name = attr_name.replace('__FILE__', '')
45
+
46
+ formDataObject.append("#{ @config.resource }#{ attr_name }", attr_value)
47
+
48
+ return formDataObject
49
+
50
+
51
+
52
+
@@ -0,0 +1,142 @@
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
+
9
+ # -----------------------------------------------------------------------------
10
+ # REST ARRAY STORE
11
+ # -----------------------------------------------------------------------------
12
+ class @RestArrayStore extends ArrayStore
13
+ # initial store configuration
14
+ _initialize_database: ->
15
+ @dataFetchLock = false
16
+ @ajaxConfig = {}
17
+
18
+
19
+ # generate rest url for resource
20
+ _resource_url: (type, id) ->
21
+ objectPath = if id then "/#{ id }" else ''
22
+ "#{ @config.path }#{ objectPath }"
23
+
24
+
25
+ # do requests to database api
26
+ _ajax: (type, id, data, success, error) ->
27
+ options = $.extend @ajaxConfig,
28
+ url: @_resource_url(type, id)
29
+ type: type
30
+ data: data
31
+ success: (data, textStatus, jqXHR) =>
32
+ success?(data)
33
+ setTimeout ( => @dataFetchLock = false ), 350
34
+ #@dataFetchLock = false
35
+ error: (jqXHR, textStatus, errorThrown ) =>
36
+ error?(jqXHR.responseJSON)
37
+ @dataFetchLock = false
38
+
39
+ @dataFetchLock = true
40
+ $.ajax options
41
+
42
+
43
+ # check how this works with sorting enabled
44
+ _sync_with_data_objects: (objects) ->
45
+ if objects.length == 0 then return @_reset_data()
46
+ if @_data.length == 0 then return ( @_add_data_object(o) for o in objects )
47
+
48
+ objectsMap = {}
49
+ (o = @_normalize_object_id(o) ; objectsMap[o._id] = o) for o in objects
50
+
51
+ objectIds = $.map objects, (o) -> o._id
52
+ dataObjectIds = $.map @_data, (o) -> o._id
53
+
54
+ addObjectIds = $(objectIds).not(dataObjectIds).get()
55
+ updateDataObjectIds = $(objectIds).not(addObjectIds).get()
56
+ removeDataObjectIds = $(dataObjectIds).not(objectIds).get()
57
+
58
+ for id in removeDataObjectIds
59
+ @_remove_data_object(id)
60
+
61
+ for id in addObjectIds
62
+ @_add_data_object(objectsMap[id])
63
+
64
+ for id in updateDataObjectIds
65
+ @_update_data_object(id, objectsMap[id])
66
+
67
+
68
+ # load a single object, this is used in view when
69
+ # store has not required item
70
+ loadObject: (id, callbacks={}) ->
71
+ callbacks.onSuccess ?= $.noop
72
+ callbacks.onError ?= $.noop
73
+
74
+ @_ajax 'GET', id, {}, ((data) =>
75
+ callbacks.onSuccess(data)
76
+ ), callbacks.onError
77
+
78
+
79
+ # load objects from database, when finished
80
+ # trigger 'objects_added' event
81
+ load: (callbacks={}) ->
82
+ callbacks.onSuccess ?= $.noop
83
+ callbacks.onError ?= $.noop
84
+
85
+ @_ajax 'GET', null, {}, ((data) =>
86
+ if data.length > 0
87
+ for o in data
88
+ @_add_data_object(o)
89
+
90
+ callbacks.onSuccess(data)
91
+
92
+ $(this).trigger('objects_added', { objects: data })
93
+ ) callbacks.onError
94
+
95
+
96
+ # add new object
97
+ push: (serializedFormObject, callbacks={}) ->
98
+ callbacks.onSuccess ?= $.noop
99
+ callbacks.onError ?= $.noop
100
+
101
+ obj = @_parse_form_object(serializedFormObject)
102
+
103
+ @_ajax 'POST', null, obj, ((data) =>
104
+ @_add_data_object(data)
105
+ callbacks.onSuccess(data)
106
+ ), callbacks.onError
107
+
108
+
109
+ # update objects attributes
110
+ update: (id, serializedFormObject, callbacks={}) ->
111
+ callbacks.onSuccess ?= $.noop
112
+ callbacks.onError ?= $.noop
113
+
114
+ obj = @_parse_form_object(serializedFormObject)
115
+
116
+ @_ajax 'PUT', id, obj, ((data) =>
117
+ @_update_data_object(id, data)
118
+ callbacks.onSuccess(data)
119
+ ), callbacks.onError
120
+
121
+
122
+ # delete object
123
+ remove: (id, callbacks={}) ->
124
+ callbacks.onSuccess ?= $.noop
125
+ callbacks.onError ?= $.noop
126
+
127
+ @_ajax 'DELETE', id, {}, ( =>
128
+ @_remove_data_object(id)
129
+ callbacks.onSuccess()
130
+ ), callbacks.onError
131
+
132
+
133
+ # reset data and load it again
134
+ reset: ->
135
+ @_ajax 'GET', null, {}, ((data) =>
136
+ @_sync_with_data_objects(data)
137
+ $(this).trigger('objects_added', { objects: data })
138
+ ), -> chr.showError('Error while loading data.')
139
+
140
+
141
+
142
+
@@ -0,0 +1,79 @@
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
+
9
+ # -----------------------------------------------------------------------------
10
+ # REST OBJECT STORE
11
+ # -----------------------------------------------------------------------------
12
+ class @RestObjectStore extends ObjectStore
13
+ _initialize_database: ->
14
+ @dataFetchLock = false
15
+ @ajaxConfig = {}
16
+
17
+
18
+ # generate rest url for resource
19
+ _resource_url: -> @config.path
20
+
21
+
22
+ # get regular javascript object from serialized form object,
23
+ # which uses special format for object names for support of:
24
+ # - files
25
+ # - lists
26
+ # - nested objects
27
+ _parse_form_object: (serializedFormObject) ->
28
+ # this is very basic and have to be expanded to support all form inputs:
29
+ # - lists, files, nested objects
30
+ object = {}
31
+ for key, value of serializedFormObject
32
+ fieldName = key.replace('[', '').replace(']', '')
33
+ object[fieldName] = value
34
+ return object
35
+
36
+
37
+ # do requests to database api
38
+ _ajax: (type, data, success, error) ->
39
+ options = $.extend @ajaxConfig,
40
+ url: @_resource_url()
41
+ type: type
42
+ data: data
43
+ success: (data, textStatus, jqXHR) =>
44
+ success?(data)
45
+ @dataFetchLock = false
46
+ error: (jqXHR, textStatus, errorThrown ) =>
47
+ error?(jqXHR.responseJSON)
48
+ @dataFetchLock = false
49
+
50
+ @dataFetchLock = true
51
+ $.ajax options
52
+
53
+
54
+ # load a single object, this is used in view when
55
+ # store has not required item
56
+ loadObject: (callbacks={}) ->
57
+ callbacks.onSuccess ?= $.noop
58
+ callbacks.onError ?= $.noop
59
+
60
+ @_ajax 'GET', {}, ((data) =>
61
+ callbacks.onSuccess(data)
62
+ ), callbacks.onError
63
+
64
+
65
+ # update objects attributes
66
+ update: (id, serializedFormObject, callbacks={}) ->
67
+ callbacks.onSuccess ?= $.noop
68
+ callbacks.onError ?= $.noop
69
+
70
+ obj = @_parse_form_object(serializedFormObject)
71
+
72
+ @_ajax 'PUT', obj, ((data) =>
73
+ @_data = data
74
+ callbacks.onSuccess(data)
75
+ ), callbacks.onError
76
+
77
+
78
+
79
+