lanes 0.5.6 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -2
  3. data/client/lanes/Config.coffee +6 -5
  4. data/client/lanes/access/LoginDialog.cjsx +4 -2
  5. data/client/lanes/access/Roles.coffee +3 -0
  6. data/client/lanes/access/screens/user-management/Editor.cjsx +1 -1
  7. data/client/lanes/components/grid/Body.cjsx +4 -3
  8. data/client/lanes/components/grid/Grid.cjsx +4 -3
  9. data/client/lanes/components/grid/Header.cjsx +1 -1
  10. data/client/lanes/components/grid/PopOverMixin.cjsx +6 -6
  11. data/client/lanes/components/grid/Selections.cjsx +16 -6
  12. data/client/lanes/components/grid/Toolbar.cjsx +1 -1
  13. data/client/lanes/components/grid/styles.scss +21 -5
  14. data/client/lanes/components/modal/Modal.cjsx +18 -9
  15. data/client/lanes/components/record-finder/Clause.cjsx +1 -2
  16. data/client/lanes/components/record-finder/Dialog.cjsx +11 -3
  17. data/client/lanes/components/record-finder/RecordFinder.cjsx +14 -7
  18. data/client/lanes/components/record-finder/styles.scss +1 -0
  19. data/client/lanes/components/select-field/SelectField.cjsx +36 -10
  20. data/client/lanes/components/select-field/styles.scss +7 -0
  21. data/client/lanes/components/shared/Checkbox.cjsx +34 -0
  22. data/client/lanes/components/shared/DateTime.cjsx +3 -2
  23. data/client/lanes/components/shared/ErrorDisplay.cjsx +1 -1
  24. data/client/lanes/components/shared/FieldMixin.cjsx +26 -15
  25. data/client/lanes/components/shared/FieldSet.cjsx +1 -1
  26. data/client/lanes/components/shared/FieldWrapper.cjsx +2 -2
  27. data/client/lanes/components/shared/FormGroup.cjsx +2 -2
  28. data/client/lanes/components/shared/Icon.cjsx +31 -4
  29. data/client/lanes/components/shared/IconButton.cjsx +8 -0
  30. data/client/lanes/components/shared/ImageAsset.cjsx +8 -5
  31. data/client/lanes/components/shared/IndeterminateCheckbox.cjsx +19 -0
  32. data/client/lanes/components/shared/InputFieldMixin.cjsx +6 -0
  33. data/client/lanes/components/shared/JobProgress.cjsx +4 -3
  34. data/client/lanes/components/shared/NetworkActivityOverlay.cjsx +1 -1
  35. data/client/lanes/components/shared/ScreenWrapper.cjsx +1 -1
  36. data/client/lanes/components/shared/ToggleField.cjsx +2 -1
  37. data/client/lanes/components/shared/Tooltip.cjsx +10 -2
  38. data/client/lanes/components/shared/fields.scss +7 -2
  39. data/client/lanes/components/toolbar/RemoteChangeSets.cjsx +10 -7
  40. data/client/lanes/components/toolbar/SaveButton.cjsx +5 -4
  41. data/client/lanes/components/toolbar/Toolbar.cjsx +18 -19
  42. data/client/lanes/components/toolbar/changes-notification.scss +3 -1
  43. data/client/lanes/components/toolbar/styles.scss +6 -3
  44. data/client/lanes/lib/HotReload.coffee +1 -1
  45. data/client/lanes/lib/all.js +1 -1
  46. data/client/lanes/lib/dom.coffee +14 -1
  47. data/client/lanes/lib/format.coffee +3 -0
  48. data/client/lanes/lib/loader.coffee +0 -2
  49. data/client/lanes/lib/{dom-polyfills.coffee → polyfills.coffee} +3 -0
  50. data/client/lanes/lib/utilFunctions.coffee +5 -7
  51. data/client/lanes/models/Asset.coffee +33 -20
  52. data/client/lanes/models/AssociationMap.coffee +5 -6
  53. data/client/lanes/models/Base.coffee +5 -2
  54. data/client/lanes/models/Collection.coffee +23 -2
  55. data/client/lanes/models/PubSub.coffee +51 -13
  56. data/client/lanes/models/Query.coffee +53 -42
  57. data/client/lanes/models/State.coffee +5 -4
  58. data/client/lanes/models/Sync.coffee +5 -3
  59. data/client/lanes/models/index.js +1 -1
  60. data/client/lanes/models/mixins/TrackCollectionRemovals.coffee +17 -0
  61. data/client/lanes/models/query/ArrayResult.coffee +16 -17
  62. data/client/lanes/models/query/CollectionResult.coffee +4 -4
  63. data/client/lanes/react/Component.coffee +14 -13
  64. data/client/lanes/react/Root.cjsx +1 -1
  65. data/client/lanes/react/Screen.coffee +1 -0
  66. data/client/lanes/react/Viewport.coffee +52 -16
  67. data/client/lanes/react/mixins/Access.coffee +5 -0
  68. data/client/lanes/react/mixins/Data.coffee +52 -144
  69. data/client/lanes/react/mixins/MonitorSize.coffee +1 -1
  70. data/client/lanes/react/mixins/ReadEditingState.coffee +3 -0
  71. data/client/lanes/screens/Commands.coffee +1 -1
  72. data/client/lanes/screens/CommonComponents.cjsx +2 -2
  73. data/client/lanes/screens/Definitions.coffee +3 -1
  74. data/client/lanes/screens/SystemSettings.cjsx +13 -18
  75. data/client/lanes/screens/UserPreferences.cjsx +1 -1
  76. data/client/lanes/styles/fonts.scss +2 -3
  77. data/client/lanes/styles/global/styles.scss +2 -1
  78. data/client/lanes/testing/TestObjects.coffee +6 -2
  79. data/client/lanes/vendor/action_cable.js +590 -0
  80. data/client/lanes/vendor/development/calendar.js +56 -56
  81. data/client/lanes/vendor/development/commons.js +7819 -6690
  82. data/client/lanes/vendor/development/data.js +1877 -1455
  83. data/client/lanes/vendor/development/helpers.js +39 -82
  84. data/client/lanes/vendor/development/toggle.js +20 -20
  85. data/client/lanes/vendor/development/ui.js +14629 -14261
  86. data/client/lanes/vendor/development/widgets.js +3146 -2173
  87. data/client/lanes/vendor/index.js +1 -2
  88. data/client/lanes/vendor/production/calendar.js +56 -56
  89. data/client/lanes/vendor/production/commons.js +6352 -6185
  90. data/client/lanes/vendor/production/data.js +1871 -1456
  91. data/client/lanes/vendor/production/toggle.js +20 -20
  92. data/client/lanes/vendor/production/ui.js +14694 -14286
  93. data/client/lanes/vendor/production/widgets.js +3139 -2166
  94. data/client/lanes/vendor/standalone/index.js +5666 -4586
  95. data/client/lanes/vendor/styles/widgets.scss +5 -5
  96. data/client/lanes/workspace/Layout.cjsx +1 -10
  97. data/client/lanes/workspace/Navbar.cjsx +8 -13
  98. data/client/lanes/workspace/ScreenView.cjsx +3 -4
  99. data/client/lanes/workspace/ScreensMenu.cjsx +8 -19
  100. data/client/lanes/workspace/Tabs.cjsx +6 -14
  101. data/db/migrate/02_create_assets.rb +1 -3
  102. data/lanes.gemspec +16 -11
  103. data/lib/lanes/access/test_fixture_extensions.rb +6 -10
  104. data/lib/lanes/access/track_modifications.rb +24 -0
  105. data/lib/lanes/api/cable.rb +49 -0
  106. data/lib/lanes/api/controller_base.rb +52 -9
  107. data/lib/lanes/api/default_routes.rb +9 -0
  108. data/lib/lanes/api/generic_controller.rb +2 -9
  109. data/lib/lanes/api/handlers/asset.rb +5 -6
  110. data/lib/lanes/api/helper_methods.rb +3 -2
  111. data/lib/lanes/api/javascript_processor.rb +6 -5
  112. data/lib/lanes/api/pub_sub.rb +14 -32
  113. data/lib/lanes/api/request_wrapper.rb +1 -1
  114. data/lib/lanes/api/root.rb +4 -7
  115. data/lib/lanes/api/routing.rb +3 -3
  116. data/lib/lanes/api/sprockets_extension.rb +1 -1
  117. data/lib/lanes/api.rb +4 -0
  118. data/lib/lanes/asset.rb +25 -25
  119. data/lib/lanes/concerns/asset_uploader.rb +25 -47
  120. data/lib/lanes/concerns/export_scope.rb +5 -7
  121. data/lib/lanes/concerns/queries.rb +7 -3
  122. data/lib/lanes/concerns/set_attribute_data.rb +12 -6
  123. data/lib/lanes/configuration.rb +4 -7
  124. data/lib/lanes/db.rb +3 -1
  125. data/lib/lanes/guard_tasks.rb +2 -1
  126. data/lib/lanes/hot_reload_plugin.rb +2 -1
  127. data/lib/lanes/job.rb +0 -1
  128. data/lib/lanes/logger.rb +3 -3
  129. data/lib/lanes/model.rb +0 -3
  130. data/lib/lanes/spec_helper.rb +1 -1
  131. data/lib/lanes/system_settings.rb +8 -14
  132. data/lib/lanes/version.rb +1 -1
  133. data/npm-build/data.js +1 -0
  134. data/npm-build/package.json +21 -19
  135. data/npm-build/ui.js +3 -0
  136. data/npm-build/update-dayz +2 -2
  137. data/npm-build/update-model-bindings.js +5 -0
  138. data/spec/command-reference-files/initial/Gemfile +1 -1
  139. data/spec/command-reference-files/initial/config/database.yml +1 -0
  140. data/spec/command-reference-files/initial/config/lanes.rb +3 -3
  141. data/spec/command-reference-files/screen/spec/appy-app/screens/ready-set-go/ReadySetGoSpec.coffee +1 -1
  142. data/spec/lanes/components/grid/GridSpec.coffee +2 -2
  143. data/spec/lanes/components/grid/PopoverEditorSpec.coffee +11 -12
  144. data/spec/lanes/components/select-field/SelectFieldSpec.coffee +6 -4
  145. data/spec/lanes/components/shared/NetworkActivityOverlaySpec.coffee +1 -1
  146. data/spec/lanes/models/PubSubSpec.coffee +6 -8
  147. data/spec/lanes/models/QuerySpec.coffee +19 -0
  148. data/spec/lanes/react/mixins/DataSpec.coffee +18 -16
  149. data/spec/server/api/coffeescript_processor_spec.rb +1 -1
  150. data/spec/server/api/controller_base_spec.rb +77 -0
  151. data/spec/server/api/pub_sub_spec.rb +9 -0
  152. data/spec/server/asset_spec.rb +23 -18
  153. data/spec/server/concerns/export_scope_spec.rb +2 -1
  154. data/spec/server/concerns/exported_limits_spec.rb +14 -9
  155. data/spec/server/concerns/set_attribute_data_spec.rb +17 -0
  156. data/spec/server/spec_helper.rb +29 -11
  157. data/templates/config/database.yml +1 -0
  158. data/templates/spec/client/Screen.coffee +1 -1
  159. data/views/specs.erb +4 -1
  160. metadata +138 -89
  161. data/client/lanes/vendor/message-bus-ajax.js +0 -44
  162. data/client/lanes/vendor/message-bus.js +0 -414
@@ -1,37 +1,43 @@
1
+ IMAGES = [
2
+ "image/png", "image/jpg", "image/gif"
3
+ ]
4
+
5
+ IS_IMAGE = (content_type) ->
6
+ !!(content_type && IMAGES.indexOf(content_type) isnt -1)
7
+
1
8
  class Lanes.Models.Asset extends Lanes.Models.Base
2
9
 
3
10
  props:
4
11
  id: 'integer'
5
12
  order: 'integer'
6
- thumbnail: 'object'
7
- medium: 'object'
8
- original: 'object'
9
- metadata: 'object'
13
+ file_data: 'object'
10
14
 
11
15
  session:
12
- data: 'string'
13
- blob: 'object'
14
- owner: 'object'
15
- parent: 'object'
16
+ data: 'string'
17
+ blob: 'object'
18
+ owner: 'object'
19
+ parent: 'object'
20
+ metadata: 'object'
16
21
  parent_association: 'string'
17
22
 
18
23
  derived:
19
24
  original_url:
20
- deps: ['data', 'original'], fn: ->
21
- if @data then @data else @original?.url
25
+ deps: ['data', 'file_data'], fn: ->
26
+ if @data then @data else @urlFor('original')
22
27
  medium_url:
23
- deps: ['data', 'medium'], fn: ->
24
- if @data then @data else @medium?.url
28
+ deps: ['data', 'file_data'], fn: ->
29
+ if @data then @data else @urlFor('medium')
25
30
  thumbnail_url:
26
- deps: ['data', 'thumbnail'], fn: ->
27
- if @data then @data else @thumbnail?.url
31
+ deps: ['data', 'file_data'], fn: ->
32
+ if @data then @data else @urlFor('thumbnail')
28
33
 
29
34
  hasImage:
30
- deps: ['data', 'original', 'metadata'], fn: ->
31
- @metadata?.content_type?.includes?('image')
35
+ deps: ['data', 'file_data'], fn: ->
36
+ (@data && IS_IMAGE(@metadata.content_type)) ||
37
+ (@file_data && IS_IMAGE(@file_data.original
38
+ .metadata.mime_type))
32
39
  events:
33
40
  'change:blob': 'onBlobChange'
34
- 'parent:save': 'onParentSave'
35
41
 
36
42
  initialize: ->
37
43
  @on('change:parent', =>
@@ -40,11 +46,18 @@ class Lanes.Models.Asset extends Lanes.Models.Base
40
46
  @listenTo(@parent, 'save', @save)
41
47
  )
42
48
 
49
+ baseUrl: ->
50
+ Lanes.config.api_path + '/asset'
51
+
52
+ urlFor: (type) ->
53
+ data = @file_data?[type]
54
+ if data then "#{@baseUrl()}/#{data.id}" else undefined
43
55
 
44
56
  onBlobChange: ->
45
57
  if @blob
46
- @metadata ||= {}
47
- @metadata.content_type = @blob.type
58
+ @metadata = {
59
+ content_type: @blob.type
60
+ }
48
61
  reader = new FileReader()
49
62
  reader.onloadend = (ev) =>
50
63
  @data = reader.result if ev.type is 'loadend'
@@ -69,7 +82,6 @@ class Lanes.Models.Asset extends Lanes.Models.Base
69
82
  url = Lanes.config.api_path + '/asset'
70
83
 
71
84
  Lanes.Vendor.xhr.post(url, {body: form}, (err, resp, body) =>
72
- @blob = null
73
85
  if err
74
86
  @errors = { http: err.message }
75
87
  else
@@ -77,5 +89,6 @@ class Lanes.Models.Asset extends Lanes.Models.Base
77
89
  if reply.errors or reply.success is false
78
90
  @errors = reply.errors
79
91
  else
92
+ @metadata = @blob = @data = null
80
93
  @set( reply.data )
81
94
  )
@@ -46,7 +46,7 @@ class Lanes.Models.AssociationMap
46
46
  definition = @definitions[name]
47
47
  options = { parent: model }
48
48
  if definition.inverse
49
- options[ definition.inverse.name ] = model
49
+ options[ definition.inverse ] = model
50
50
  if definition.options
51
51
  _.extend(options, Lanes.u.resultsFor(model, definition.options))
52
52
  options
@@ -75,14 +75,12 @@ class Lanes.Models.AssociationMap
75
75
  options = association.getOptions(name, this)
76
76
  options.filter ||= {}
77
77
  options.filter[fk] = this.get(pk)
78
- options.inverse =
79
- name: definition.inverse
80
- without: name
81
78
 
82
79
  if true == target_class::isCollection
83
80
  new target_class(options.models || [], options)
84
81
  else
85
82
  options.model = target_class
83
+ options.association_name=name
86
84
  new Lanes.Models.AssociationCollection(options.models || [], options)
87
85
 
88
86
  # returns a collection for the given association.
@@ -181,9 +179,10 @@ class Lanes.Models.AssociationMap
181
179
  continue if def_options.readOnly or
182
180
  (options.onlyAssociations and not _.includes(options.onlyAssociations, name))
183
181
  assoc = model[name]
182
+ force = _.includes(options.includeAssociations, name)
184
183
  ret[name] = assoc.dataForSave(
185
- _.extend({}, options, def_options)
186
- ) if _.result(assoc, 'isDirty')
184
+ _.extend({}, options, def_options, saveAll: force)
185
+ ) if _.result(assoc, 'isDirty') or force
187
186
  ret
188
187
 
189
188
  serialize: (model, options = {depth: 1}) ->
@@ -18,7 +18,7 @@ class BaseModel
18
18
  derived:
19
19
  isSavable:
20
20
  deps: ['invalidAttributes'], fn: ->
21
- _.isEmpty @invalidAttributes #_calculateInvalidAttributes()
21
+ _.isEmpty @invalidAttributes
22
22
 
23
23
  hasErrors:
24
24
  deps: ['errors'], fn: -> not _.isEmpty(@errors)
@@ -105,7 +105,7 @@ class BaseModel
105
105
 
106
106
  clonedAttributes: ->
107
107
  attributes = @serialize()
108
- _.extend(attributes, @getAttributes(session: true) )
108
+ _.extend(attributes, @getAttributes(session: true, derived: false) )
109
109
 
110
110
  attributeType: (name) ->
111
111
  @_definition[name].type
@@ -261,6 +261,9 @@ class BaseModel
261
261
 
262
262
  klass::session['created_at'] ||= 'date'
263
263
  klass::session['updated_at'] ||= 'date'
264
+ # these columns are included when loaded with the 'with_user_logins' scope
265
+ klass::session['created_by_user.login'] ||= 'string'
266
+ klass::session['updated_by_user.login'] ||= 'string'
264
267
 
265
268
  if klass::associations
266
269
  klass::associations = new Lanes.Models.AssociationMap(klass)
@@ -54,7 +54,6 @@ class ModelsCollection
54
54
 
55
55
  constructor: ->
56
56
  @_isLoaded = false
57
- @errors = []
58
57
  Lanes.Vendor.Ampersand.Collection.apply(this, arguments)
59
58
  this.on('add remove reset', this._triggerLengthEvent )
60
59
 
@@ -103,6 +102,7 @@ class ModelsCollection
103
102
 
104
103
  # Sets the attribute data from a server respose
105
104
  setFromServer: (data, options, method) ->
105
+ options ||= {}
106
106
  @_isLoaded = true
107
107
  if 'delete' == method
108
108
  models = _.map(_.map(options.originalData, 'id'), (id) =>
@@ -177,7 +177,11 @@ class SubCollection
177
177
  setFromServer: (data, options, type) ->
178
178
  @collection.setFromServer(data, options, type)
179
179
 
180
- Lanes.Models.SubCollection = Lanes.lib.MakeBaseClass( Lanes.Vendor.Ampersand.SubCollection, SubCollection )
180
+ Lanes.Models.SubCollection = Lanes.lib.MakeBaseClass(
181
+ Lanes.Vendor.Ampersand.SubCollection.extend(Lanes.Vendor.Ampersand.LDCollection), SubCollection
182
+ )
183
+
184
+
181
185
 
182
186
  Lanes.Models.BasicCollection = Lanes.lib.MakeBaseClass(
183
187
  Lanes.Vendor.Ampersand.Collection.extend(Lanes.Vendor.Ampersand.LDCollection), BasicCollection
@@ -196,6 +200,10 @@ class Lanes.Models.AssociationCollection extends Lanes.Models.Collection
196
200
  @associationFilter = @options.filter
197
201
  super
198
202
 
203
+ mixins: [
204
+ Lanes.Models.Mixins.TrackRemovals
205
+ ]
206
+
199
207
  _prepareModel: (attrs, options = {}) ->
200
208
  if @associationFilter
201
209
  _.extend(attrs, @associationFilter)
@@ -209,3 +217,16 @@ class Lanes.Models.AssociationCollection extends Lanes.Models.Collection
209
217
  _.extend(options.query, @associationFilter)
210
218
  _.extend(options, @options)
211
219
  super(options)
220
+
221
+ isDirty: ->
222
+ super || @hasRemovedModels()
223
+
224
+ dataForSave: ->
225
+ data = super
226
+ for id in @getRemovedModelIds()
227
+ data.push({"#{@model.prototype.idAttribute}": id, _delete: true})
228
+ data
229
+
230
+ setFromServer: (data, options, method) ->
231
+ @clearRemovedModelIds()
232
+ super
@@ -8,8 +8,7 @@ class ModelConfig
8
8
 
9
9
  add: (model) ->
10
10
  if @count is 0
11
- Lanes.log.info "[pubsub] subscribe to: #{@channel}"
12
- Lanes.Models.PubSub.mb?.subscribe(@channel, @mbCallBack(@models))
11
+ Lanes.Models.PubSub.channel?.subscribe(@channel) #, @mbCallBack(@models))
13
12
  @count += 1
14
13
  config = @modelConfig(model)
15
14
  config.count += 1
@@ -32,14 +31,11 @@ class ModelConfig
32
31
  _.remove(@models, {model: model}) if config.count is 0
33
32
 
34
33
  unsubscribe: ->
35
- Lanes.log.info "[pubsub] unsubscribe from: #{@channel}"
36
- Lanes.Models.PubSub.mb?.unsubscribe( @channel )
34
+ Lanes.Models.PubSub.channel?.unsubscribe( @channel )
37
35
  delete @type.records[@id]
38
36
 
39
- mbCallBack: (models) ->
40
- (changes) ->
41
- config.model.addChangeSet(changes) for config in models
42
-
37
+ onChange: (data) ->
38
+ config.model.addChangeSet(data) for config in @models
43
39
 
44
40
  class ModelType
45
41
 
@@ -54,6 +50,11 @@ class ModelType
54
50
  remove: (model) ->
55
51
  @records[model.id]?.remove(model)
56
52
 
53
+ onChange: (id, data) ->
54
+ @records[id].onChange(data)
55
+
56
+ unsubscribeAll: ->
57
+ record.unsubscribe() for id, record of @records
57
58
 
58
59
  class ModelTypesCollection extends Lanes.Models.BasicCollection
59
60
 
@@ -65,6 +66,30 @@ class ModelTypesCollection extends Lanes.Models.BasicCollection
65
66
  models = this.get(path) || this.add(id: path)
66
67
 
67
68
 
69
+ CableChannel = {
70
+ connected: ->
71
+ @subscribe 'file-change', ->
72
+ Lanes.lib.HotReload.initiate(changes)
73
+
74
+ subscribe: (channel) ->
75
+ Lanes.log.info "[pubsub] subscribe to: #{channel}"
76
+
77
+ @perform("on", {channel})
78
+
79
+ unsubscribe: (channel) ->
80
+ Lanes.log.info "[pubsub] unsubscribe from: #{channel}"
81
+ @perform("off", {channel})
82
+
83
+ received: (data) ->
84
+ [channel, model, id] = data.channel.match(/(.*)\/(\d+)/)
85
+ Lanes.log.info "[pubsub] change recvd for: #{channel}"
86
+
87
+ Lanes.Models.PubSub.onChange(
88
+ model, id, _.omit(data, 'channel')
89
+ )
90
+
91
+ }
92
+
68
93
  Lanes.Models.PubSub = {
69
94
 
70
95
  types: new ModelTypesCollection
@@ -85,9 +110,22 @@ Lanes.Models.PubSub = {
85
110
  @types = new ModelTypesCollection
86
111
 
87
112
  initialize: ->
88
- @mb ||= MessageBus.noConflict()
89
- @mb.start()
90
- @mb.subscribe("/file-change", (changes) ->
91
- Lanes.lib.HotReload.initiate(changes)
92
- )
113
+ Lanes.current_user.on 'change:isLoggedIn', _.bind(@onLoginChange, @)
114
+ @onLoginChange() if Lanes.current_user.isLoggedIn
115
+
116
+ onChange: (path, id , data) ->
117
+ @types.get(path).onChange(id, data)
118
+
119
+ onLoginChange: ->
120
+ if Lanes.current_user.isLoggedIn
121
+ url = window.location.protocol.replace('http', 'ws') +
122
+ "//#{Lanes.config.api_host}#{Lanes.config.api_path}/ws"
123
+ @cable = ActionCable.createConsumer(url)
124
+ @channel = @cable.subscriptions.create "Lanes::API::PubSub", CableChannel
125
+ else
126
+ Lanes.Models.PubSub.types.each (t) -> t.unsubscribeAll()
127
+ delete @channel
128
+ @cable.disconnect()
129
+ delete @cable
130
+
93
131
  }
@@ -4,7 +4,8 @@
4
4
  ##= require ./query/CollectionResult
5
5
 
6
6
  class Field extends Lanes.Models.Base
7
- pubsub: false
7
+ registerForPubSub: false
8
+
8
9
  constructor: (attributes) ->
9
10
  super( _.defaults( attributes, {
10
11
  title: _.titleize(_.humanize(attributes.id))
@@ -25,6 +26,7 @@ class Field extends Lanes.Models.Base
25
26
  sortBy: type: 'any'
26
27
  component: type: 'function'
27
28
  onColumnClick: type: 'function'
29
+ fetchIndex: type: 'integer'
28
30
 
29
31
  derived:
30
32
  default_value: deps: ['id'], fn: ->
@@ -38,12 +40,6 @@ class Field extends Lanes.Models.Base
38
40
  deps: ['model_field'], fn: ->
39
41
  type = @model_field?.type || 'string'
40
42
  if type == "code" then "string" else type
41
- fetchIndex:
42
- fn: ->
43
- fetchIndex = 0
44
- for field, index in this.collection.models
45
- return fetchIndex if this == field
46
- fetchIndex += 1 if field.query
47
43
 
48
44
  validValue: (value) ->
49
45
  if this.type == 'n'
@@ -58,6 +54,7 @@ class AvailableFields extends Lanes.Models.Collection
58
54
 
59
55
  constructor: (models, options) ->
60
56
  @query = options.query
57
+ this.on('add remove reset', @calculateFetchIndexes)
61
58
  this.on('change', (changing) ->
62
59
  return unless changing.selected
63
60
  for model in @models
@@ -65,10 +62,15 @@ class AvailableFields extends Lanes.Models.Collection
65
62
  )
66
63
  super
67
64
  @visible = @subcollection(where: {visible: true})
65
+ @calculateFetchIndexes()
68
66
 
67
+ calculateFetchIndexes: ->
68
+ index = 0
69
+ for field in @models
70
+ field.fetchIndex = if field.query then index++ else undefined
69
71
 
70
72
  class Operator extends Lanes.Models.Base
71
- pubsub: false
73
+ registerForPubSub: false
72
74
 
73
75
  session:
74
76
  id: 'string'
@@ -77,17 +79,14 @@ class Operator extends Lanes.Models.Base
77
79
  field: 'object'
78
80
  selected: 'boolean'
79
81
 
80
- derived:
81
- valid:
82
- deps: ['types', 'field']
83
- fn: ->
84
- !this.types || ( this.field && _.includes(this.types, this.field.type) )
82
+ validForField: (field) ->
83
+ _.isEmpty(@types) or _.includes(this.types, field.type)
85
84
 
86
85
 
87
86
  class Operators extends Lanes.Models.Collection
88
87
  model: Operator
89
88
 
90
- constructor: ->
89
+ constructor: (models, attrs) ->
91
90
  super
92
91
  this.add([
93
92
  { id: 'like', name: 'Starts With', types: Lanes.Models.Query.LIKE_QUERY_TYPES }
@@ -100,17 +99,18 @@ class Operators extends Lanes.Models.Collection
100
99
  for model in @models
101
100
  model.selected = false unless model == changing
102
101
  )
102
+ @setField(attrs.parent.query.initialField)
103
+
103
104
  setField: (field) ->
104
- this.invoke('set', 'field', field)
105
+ @field = field
106
+ @valid = @subcollection(filter: (op) => op.validForField(@field) )
105
107
  selected = this.findWhere(selected: true)
106
- if selected && !selected.valid
107
- selected.selected = false
108
- this.findWhere(valid:true).selected = true
109
-
108
+ unless selected && selected.validForField(field)
109
+ @valid.at(0)?.selected = true
110
110
 
111
111
 
112
112
  class Clause extends Lanes.Models.Base
113
- pubsub: false
113
+ registerForPubSub: false
114
114
 
115
115
  session:
116
116
  value : { type: 'string', default: '' }
@@ -119,9 +119,7 @@ class Clause extends Lanes.Models.Base
119
119
 
120
120
  associations:
121
121
  operators : { collection: Operators }
122
- fields:
123
- collection: AvailableFields, options: ->
124
- query: this.collection.query
122
+ fields: { collection: AvailableFields }
125
123
 
126
124
  derived:
127
125
  description:
@@ -138,15 +136,12 @@ class Clause extends Lanes.Models.Base
138
136
  constructor: (options) ->
139
137
  super
140
138
  this.fields.reset(options.available_fields.models)
141
- @operators.field = @fields.first()
142
139
  @fields.on('change:selected', this.setField, this)
143
140
  @operators.on('change:selected', this.setOperator, this)
144
- field = @fields.findWhere(visible: true)
145
- if field
141
+ @operator = @operators.findWhere(selected: true)
142
+ if (field = @fields.findWhere(visible: true))
146
143
  field.selected = true
147
144
  @setField(field)
148
- operator = @operators.find (o) -> o.valid
149
- operator?.selected = true
150
145
 
151
146
  setFromView: (type, val) ->
152
147
  if type == "fields" || type == "operators"
@@ -157,7 +152,7 @@ class Clause extends Lanes.Models.Base
157
152
  setField: (field) ->
158
153
  return unless field.selected
159
154
  @operators.setField( field )
160
- this.field = field
155
+ @field = field
161
156
 
162
157
  setOperator: (operator) ->
163
158
  return unless operator.selected
@@ -172,7 +167,7 @@ class Clause extends Lanes.Models.Base
172
167
  value = this.get('value')
173
168
  value += '%' if 'like' == op
174
169
  value = parseFloat(value) if @field.type == "n"
175
- param[ this.field.id ] = if 'eq' == op then value else { op: op, value: value }
170
+ param[ @field.id ] = if 'eq' == op then value else { op: op, value: value }
176
171
  param
177
172
 
178
173
 
@@ -185,11 +180,14 @@ class Clauses extends Lanes.Models.Collection
185
180
  @query = options.query
186
181
  @fields = options.query.fields
187
182
 
183
+ session:
184
+ field: 'state'
185
+
188
186
  # needs to inherit from Base so network events will be listened to
189
187
  class Lanes.Models.Query extends Lanes.Models.Base
190
- pubsub: false
188
+ registerForPubSub: false
191
189
 
192
- @LIKE_QUERY_TYPES: ['string', 'code']
190
+ @LIKE_QUERY_TYPES: ['string', 'code', 'visible_id']
193
191
  @LESS_THAN_QUERY_TYPES: ['integer', 'bigdec', 'number']
194
192
  @GREATER_THAN_QUERY_TYPES: ['integer', 'bigdec', 'number']
195
193
 
@@ -199,8 +197,6 @@ class Lanes.Models.Query extends Lanes.Models.Base
199
197
 
200
198
  session:
201
199
  src: 'any'
202
- fields: 'collection'
203
- clauses: 'collection'
204
200
  initialField: 'state'
205
201
  idIndex: 'number'
206
202
  initialFieldIndex: 'number'
@@ -213,6 +209,10 @@ class Lanes.Models.Query extends Lanes.Models.Base
213
209
  changeCount: {type: 'integer', default: 0}
214
210
  sortAscending: ['boolean', true, true]
215
211
 
212
+ associations:
213
+ fields: {collection: AvailableFields, inverse: 'query'}
214
+ clauses: {collection: Clauses, inverse: 'query'}
215
+
216
216
  derived:
217
217
  isCollection:
218
218
  deps: ['src'], fn: -> Lanes.u.isCollection(@src)
@@ -238,7 +238,8 @@ class Lanes.Models.Query extends Lanes.Models.Base
238
238
 
239
239
  constructor: (options = {}) ->
240
240
  super
241
- @fields = new AvailableFields([], query: this)
241
+
242
+ @fields.reset()
242
243
  for col, i in options.fields
243
244
  rec = if _.isObject(col) then col else { id: col }
244
245
  @fields.add rec
@@ -246,7 +247,7 @@ class Lanes.Models.Query extends Lanes.Models.Base
246
247
  unless @idIndex?
247
248
  @idIndex = @fields.length
248
249
  @fields.add(id: @idAttribute)
249
- @clauses = new Clauses([], query: this )
250
+ # @clauses = new Clauses([], query: this )
250
251
  this.listenTo(@clauses, 'change remove reset', =>
251
252
  @results.reset()
252
253
  @results.ensureLoaded() if this.autoRetrieve
@@ -259,14 +260,21 @@ class Lanes.Models.Query extends Lanes.Models.Base
259
260
  )
260
261
 
261
262
  if @initialFieldIndex
262
- @initialField = this.fields.at(@initialFieldIndex)
263
+ @initialField = this.fields.visible.at(@initialFieldIndex)
263
264
 
264
- @initialField ||= this.fields.findWhere(id: "code") ||
265
- this.fields.findWhere(id: "visibleId") ||
266
- this.fields.first()
265
+ @initialField ||= this.fields.visible.findWhere(id: "code") ||
266
+ this.fields.visible.findWhere(id: "visible_id") ||
267
+ this.fields.visible.first()
267
268
  @reset(true)
268
269
  this
269
270
 
271
+ clonedAttributes: ->
272
+ attrs = @getAttributes(session: true, derived: false)
273
+ attrs.fields = this.fields.serialize({session: true})
274
+ attrs
275
+
276
+ markModified: -> @changeCount += 1
277
+
270
278
  reset: (silent = false) ->
271
279
  unless @defaultSort is false
272
280
  sort = @defaultSort or @fields.findWhere(visible: true).id
@@ -289,14 +297,17 @@ class Lanes.Models.Query extends Lanes.Models.Base
289
297
  ! @clauses.findWhere( isValid: false )
290
298
 
291
299
  loadModel: (model, options = {}) ->
292
- _.extend(options, _.result(this, 'syncOptions'))
300
+ _.extend(options, _.result(this, 'syncOptions'), force: true)
293
301
  model.withAssociations(options.include || [], options)
294
302
 
295
303
  loadSingle: (code, options = {}) ->
296
304
  options.query = {}
297
305
  options.query[ @initialField.id ] = code
298
306
  _.extend(options, _.result(this, 'syncOptions'))
299
- @src.fetch(options)
307
+ @trigger('request')
308
+ @src.fetch(options).then (model) =>
309
+ @trigger('load')
310
+ model
300
311
 
301
312
  defaultField: ->
302
313
  @fields.findWhere( field: @initialField )
@@ -5,10 +5,11 @@ class State
5
5
  @
6
6
 
7
7
  _bindEvent: (eventSpec, fnName) ->
8
- [kp, event] = eventSpec.split(' ')
9
- unless event
10
- event = kp; kp = ''
11
- _.get(@, kp, @).on(event, @[fnName], @)
8
+ [kp, events...] = eventSpec.split(' ')
9
+ if _.isEmpty(events)
10
+ @on(kp, @[fnName])
11
+ else
12
+ @listenTo(_.get(@, kp), events.join(' '), @[fnName])
12
13
 
13
14
 
14
15
  isState: true
@@ -61,7 +61,7 @@ Lanes.Models.Sync = {
61
61
 
62
62
 
63
63
  perform: (method, options = {}) ->
64
- query = {}
64
+ query = options.queryParams || {}
65
65
  for key, value of options
66
66
  query[ this.paramsMap[key] ] = value if this.paramsMap[key]
67
67
 
@@ -74,12 +74,14 @@ Lanes.Models.Sync = {
74
74
 
75
75
  # Ensure that we have a URL.
76
76
  options.url or Lanes.Models.Sync.urlError()
77
- options.url += '.json'
77
+
78
+ options.url = '//' + Lanes.config.api_host + options.url + '.json'
79
+
78
80
  unless _.isEmpty(query)
79
81
  options.url += '?' + Lanes.lib.objToParam(query)
80
82
 
81
83
  options.headers ||= {}
82
-
84
+ options.withCredentials = true
83
85
  if Lanes.config.csrf_token
84
86
  options.headers['X_CSRF_TOKEN'] = Lanes.config.csrf_token
85
87
  options.contentType = "application/json"
@@ -1,4 +1,5 @@
1
1
  //= require_self
2
+ //= require_tree ./mixins
2
3
  //= require ./Sync
3
4
  //= require ./ServerCache
4
5
  //= require ./AssociationMap
@@ -6,5 +7,4 @@
6
7
  //= require ./State
7
8
  //= require ./Base
8
9
  //= require ./Collection
9
- //= require_tree ./mixins
10
10
  //= require_tree .
@@ -0,0 +1,17 @@
1
+ Lanes.Models.Mixins.TrackRemovals = {
2
+
3
+ initialize: ->
4
+ @on('remove', (model) ->
5
+ @_removedModelIds ||= []
6
+ @_removedModelIds.push model.getId()
7
+ )
8
+
9
+ getRemovedModelIds: ->
10
+ @_removedModelIds || []
11
+
12
+ clearRemovedModelIds: ->
13
+ delete @_removedModelIds
14
+
15
+ hasRemovedModels: ->
16
+ !!@_removedModelIds
17
+ }