lanes 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +0 -1
  3. data/client/lanes/{screens/Instance.coffee → Boot.coffee} +5 -5
  4. data/client/lanes/Config.coffee +2 -0
  5. data/client/lanes/components/multi-select/MultiSelect.coffee +20 -6
  6. data/client/lanes/components/popover/PopOver.coffee +5 -2
  7. data/client/lanes/components/record-finder/RecordFinder.coffee +12 -7
  8. data/client/lanes/components/record-finder/config.json +1 -1
  9. data/client/lanes/components/select-field/SelectField.coffee +8 -8
  10. data/client/lanes/index.js +1 -0
  11. data/client/lanes/index.scss.erb +1 -1
  12. data/client/lanes/lib/MakeBaseClass.coffee +12 -0
  13. data/client/lanes/lib/loader.coffee +3 -1
  14. data/client/lanes/lib/namespace.coffee +1 -1
  15. data/client/lanes/lib/utilFunctions.coffee +13 -1
  16. data/client/lanes/models/{ModelAssociations.coffee → AssociationMap.coffee} +12 -9
  17. data/client/lanes/models/Base.coffee +40 -14
  18. data/client/lanes/models/Collection.coffee +6 -2
  19. data/client/lanes/models/EnumMap.coffee +25 -0
  20. data/client/lanes/models/PubSub.coffee +2 -2
  21. data/client/lanes/models/Query.coffee +1 -1
  22. data/client/lanes/models/ServerCache.coffee +63 -0
  23. data/client/lanes/models/Sync.coffee +11 -13
  24. data/client/lanes/models/index.js +3 -1
  25. data/client/lanes/screens/Base.coffee +1 -1
  26. data/client/lanes/screens/Definitions.coffee +1 -1
  27. data/client/lanes/screens/index.js +1 -1
  28. data/client/lanes/screens/mixins/Editing.coffee +38 -0
  29. data/client/lanes/screens/mixins/index.js +1 -0
  30. data/client/lanes/styles/plugins/overlay.scss +7 -0
  31. data/client/lanes/testing/BeforeEach.coffee +62 -0
  32. data/client/lanes/testing/ModelSaver.coffee +5 -6
  33. data/client/lanes/testing/TestModels.coffee +23 -0
  34. data/client/lanes/testing/index.js +3 -1
  35. data/client/lanes/vendor/packaged.js +5860 -26
  36. data/client/lanes/views/Base.coffee +20 -20
  37. data/client/lanes/views/FormBindings.coffee +4 -2
  38. data/client/lanes/views/RenderContext.coffee +2 -2
  39. data/client/lanes/workspace/Layout.coffee +6 -2
  40. data/client/lanes/workspace/Navbar.coffee +1 -1
  41. data/client/lanes/workspace/Pages.coffee +1 -1
  42. data/client/lanes/workspace/navbar.html +3 -1
  43. data/client/lanes/workspace/styles/header.scss +3 -1
  44. data/lib/lanes/access/authentication_provider.rb +2 -2
  45. data/lib/lanes/access/config/routes.rb +1 -1
  46. data/lib/lanes/access/extension.rb +1 -4
  47. data/lib/lanes/access/role.rb +20 -15
  48. data/lib/lanes/access/test_fixture_extensions.rb +20 -0
  49. data/lib/lanes/access.rb +3 -3
  50. data/lib/lanes/api/request_wrapper.rb +3 -2
  51. data/lib/lanes/api/sprockets_extension.rb +1 -1
  52. data/lib/lanes/api/test_specs.rb +1 -1
  53. data/lib/lanes/capistrano.rb +18 -6
  54. data/lib/lanes/command/app.rb +1 -1
  55. data/lib/lanes/command/generate_model.rb +6 -0
  56. data/lib/lanes/command/generate_view.rb +7 -6
  57. data/lib/lanes/command/named_command.rb +1 -1
  58. data/lib/lanes/concerns/all.rb +1 -0
  59. data/lib/lanes/concerns/code_identifier.rb +43 -0
  60. data/lib/lanes/concerns/set_attribute_data.rb +0 -8
  61. data/lib/lanes/configuration.rb +1 -1
  62. data/lib/lanes/db.rb +11 -9
  63. data/lib/lanes/extension.rb +1 -0
  64. data/lib/lanes/guard_tasks.rb +1 -1
  65. data/lib/lanes/model.rb +1 -0
  66. data/lib/lanes/rake_tasks.rb +0 -2
  67. data/lib/lanes/screen.rb +1 -4
  68. data/lib/lanes/spec_helper.rb +5 -2
  69. data/lib/lanes/version.rb +1 -1
  70. data/npm-build/package.json +1 -0
  71. data/npm-build/template.js +1 -0
  72. data/spec/command-reference-files/initial/Gemfile +4 -1
  73. data/spec/command-reference-files/initial/client/appy-app/Extension.coffee +11 -1
  74. data/spec/command-reference-files/initial/config/lanes.rb +4 -1
  75. data/spec/command-reference-files/initial/lib/appy-app/extension.rb +12 -1
  76. data/spec/command-reference-files/initial/lib/appy-app.rb +0 -1
  77. data/spec/command-reference-files/model/lib/appy-app/model.rb +12 -0
  78. data/spec/command-reference-files/model/lib/appy-app.rb +11 -0
  79. data/spec/command-reference-files/view/client/appy-app/views/BigView.coffee +1 -0
  80. data/spec/lanes/helpers/lanes-helpers.coffee +0 -79
  81. data/spec/lanes/models/{ModelAssociationsSpec.coffee → AssociationMapSpec.coffee} +0 -0
  82. data/spec/lanes/models/BaseSpec.coffee +25 -12
  83. data/spec/lanes/models/CollectionSpec.coffee +20 -0
  84. data/spec/lanes/models/EnumMapSpec.coffee +26 -0
  85. data/spec/server/command_spec.rb +1 -1
  86. data/templates/Gemfile +4 -1
  87. data/templates/client/Extension.coffee +11 -1
  88. data/templates/client/views/View.coffee +2 -1
  89. data/templates/config/lanes.rb +4 -1
  90. data/templates/lib/namespace/extension.rb +12 -1
  91. data/templates/lib/namespace.rb +0 -1
  92. data/templates/spec/client/views/ViewSpec.coffee +1 -1
  93. metadata +16 -6
  94. data/spec/command-reference-files/initial/lib/appy-app/models/empty.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b111e3e03f3c6f6a3bfffca0d087590b1ea0277a
4
- data.tar.gz: ac11d9b1700311f927f06d06bcbee08de31c56e2
3
+ metadata.gz: 6ed648a6329d1d1b0c9ee0e5df25d1c184b5f5e0
4
+ data.tar.gz: 023a41ef58e894aa882ae712edb9a77b12d57d73
5
5
  SHA512:
6
- metadata.gz: 3fd3233d6311c8df5041a5a39e2245f5e5099fbddde5126c8b4202e51bb79e51a25391bec8a18f9c51d13d3d34f66e7e22e5ce54a0fc88e42ea9e568bfe473ce
7
- data.tar.gz: bd91444bafa49fc750fb6750071903a4856bff5ececf54c577935b55382788798f5b574f57b91fd41f56fb80bb5dece54b208fbc981e8b23f69d3519e0ce7d48
6
+ metadata.gz: b6cfb3a4c38ec8dd175c17047aafc03ced98e6d30ce04e48f5bd5b2550aa88521cad0ac12252a00dfdaacff9c606bd68f5d3bdccf8b5882e7b396e16c2f189e6
7
+ data.tar.gz: 8befe413cd47f78796a50d344ffcd89e7021ad7d4541889a67d483b0a02c41bda1b6335de7c3263a00d30124b68dc1cd091e793ceacb9c030ed695943da95468
data/Rakefile CHANGED
@@ -24,7 +24,6 @@ end
24
24
 
25
25
 
26
26
  task :doc => 'db:environment' do
27
- env = ENV['RAILS_ENV'] || 'development'
28
27
  ENV['SCHEMA'] = 'db/schema.rb'
29
28
  ENV['DB_STRUCTURE'] = 'db/schema.rb'
30
29
  Rake::Task["db:schema:dump"].invoke
@@ -1,8 +1,4 @@
1
- Lanes.renderScreenTo = (selector, options)->
2
- return new Lanes.Screens.Instance(selector, options);
3
-
4
-
5
- class Lanes.Screens.Instance
1
+ class RootView
6
2
 
7
3
  constructor: (selector, options)->
8
4
  this.viewport = new Lanes.Views.Viewport({ selector: selector, instance: this })
@@ -48,3 +44,7 @@ class Lanes.Screens.Instance
48
44
  this.viewport.el = this.view.$el
49
45
  this.root.append( this.view.el )
50
46
  Lanes.Extensions.fireOnAvailable(this)
47
+
48
+
49
+ Lanes.renderScreenTo = (selector, options)->
50
+ return new RootView(selector, options);
@@ -4,7 +4,9 @@ class Config extends Lanes.Models.State
4
4
  session:
5
5
  csrf_token: { type: 'string', setOnce: true }
6
6
  api_path: { type: 'string', default: '', setOnce: true }
7
+ assets_path_prefix: { type: 'string', setOnce: true }
7
8
  environment: { type: 'string', setOnce: true }
9
+ initial_workspace_screen_id: { type: 'string', setOnce: true }
8
10
 
9
11
  derived:
10
12
  env:
@@ -4,6 +4,7 @@ class Lanes.Components.MultiSelect extends Lanes.Components.Base
4
4
  session:
5
5
  multiple: { type: 'boolean', default: false }
6
6
  selections: 'collection'
7
+ defaultID: 'integer'
7
8
  association: 'string'
8
9
  mappings: 'object'
9
10
 
@@ -11,7 +12,6 @@ class Lanes.Components.MultiSelect extends Lanes.Components.Base
11
12
  if options.model
12
13
  if options.association && ! options.field_name
13
14
  options.field_name = options.model.associations.pk(options.association)
14
-
15
15
  if options.field_name
16
16
  listener = {}
17
17
  listener["change:#{options.field_name}"] ='onModelAttributeChange'
@@ -26,11 +26,25 @@ class Lanes.Components.MultiSelect extends Lanes.Components.Base
26
26
  if options.choices
27
27
  @selections ||= options.choices
28
28
 
29
- @selections?.ensureLoaded?()
29
+ @selections?.ensureLoaded?().then =>
30
+ @selectDefault()
30
31
 
31
32
  @mappings = _.extend({
32
- id: 'id', title: 'title', selected: 'selected'
33
+ id: 'id', title: 'code', selected: 'selected'
33
34
  },options.mappings||{})
35
+ if @association && !@defaultID
36
+ pk = options.model[ options.model.associations.pk(this.association) ]
37
+ @defaultID ||= pk
38
+
39
+ selectDefault: ->
40
+ if @defaultID
41
+ selection = this.selectionForID(@defaultID)
42
+ else
43
+ selection = _.result(this.selections,'first')
44
+ this.select(selection) if selection
45
+
46
+ onRender: ->
47
+ this.selectDefault()
34
48
 
35
49
  unSelectAll: ->
36
50
  this.$el.find(":selected").prop('selected',false)
@@ -46,6 +60,6 @@ class Lanes.Components.MultiSelect extends Lanes.Components.Base
46
60
 
47
61
  onModelAttributeChange: (model,fkids)->
48
62
  if ! this.el.binding_is_setting
49
- _.each(fkids, (fkid)=>
50
- @select( @selectionForID( fkid ) )
51
- )
63
+ fkids = [fkids] unless _.isArray(fkids)
64
+ for fkid in fkids
65
+ this.select(selection) if selection=this.selectionForID( fkid )
@@ -40,7 +40,9 @@ class Lanes.Components.PopOver extends Lanes.Components.Base
40
40
  return this.notification.promise
41
41
 
42
42
  destroy: ->
43
+ this.isDestroying = true
43
44
  this.target.popover('destroy')
45
+ this.remove()
44
46
 
45
47
  hide:->
46
48
  this.target.popover('hide')
@@ -48,5 +50,6 @@ class Lanes.Components.PopOver extends Lanes.Components.Base
48
50
  this.destroy()
49
51
 
50
52
  _onHide: (ev)->
51
- this.notification.resolve(this)
52
- this.destroy() if this.destroyAfterHide
53
+ this.notification?.resolve(this)
54
+ if this.destroyAfterHide && !this.isDestroying
55
+ this.destroy()
@@ -37,20 +37,25 @@ class Lanes.Components.RecordFinder extends Lanes.Components.Base
37
37
  dlg.remove().record
38
38
  ).then( (record)=>
39
39
  if record
40
- if @withAssociations then record.withAssociations(@withAssociations...) else record
40
+ if _.isEmpty(@withAssociations)
41
+ record.fetch()
42
+ else
43
+ record.withAssociations(@withAssociations...)
41
44
  else
42
45
  null
43
- ).then( (record)=>
44
- @.$el.trigger("display-record", record) if record
46
+ ).then( (reply)=>
47
+ @.$el.trigger("display-record", reply.record) if reply?.record
45
48
  , (e)->Lanes.warn(e) )
46
49
 
47
50
  onKey: (ev)->
48
51
  if 13 == ev.keyCode
49
- this.runQuery(ev)
52
+ code = this.$(ev.target).val()
53
+ this.loadCode(code)
50
54
  else if @invalid_chars && @ui.input.val().match( @invalid_chars )
51
55
  @ui.input.val( @ui.input.val().replace( @invalid_chars, '' ) )
52
56
 
53
- runQuery: (ev)->
54
- code = this.$(ev.target).val()
57
+ loadCode: (code)->
55
58
  @recordQuery.loadSingle(code.toUpperCase(),{ include: @withAssociations })
56
- .then( (reply)=> this.$el.trigger("display-record", reply.record) if reply.record )
59
+ .then( (reply)=>
60
+ this.$el.trigger("display-record", reply.record) if reply.record
61
+ )
@@ -1,3 +1,3 @@
1
1
  {
2
- "depends": ["radio-group"]
2
+ "depends": ["radio-group", "modal", "grid"]
3
3
  }
@@ -17,7 +17,6 @@ class SelectOption extends Lanes.Views.Base
17
17
 
18
18
  class Lanes.Components.SelectField extends Lanes.Components.MultiSelect
19
19
 
20
-
21
20
  subviews:
22
21
  fields:
23
22
  hook: 'choices-input'
@@ -39,10 +38,8 @@ class Lanes.Components.SelectField extends Lanes.Components.MultiSelect
39
38
 
40
39
  onSelectChange: (ev)->
41
40
  if @model && @association
42
- @model.set(@association, this.selectionForID( this.ui.select.val() ))
43
-
44
- constructor: (options={})->
45
- super
41
+ selection = this.selectionForID(parseInt(this.ui.select.val()))
42
+ @model.set(@association, selection) if selection
46
43
 
47
44
  writeTemplate: ->
48
45
  multiple = if this.multiple then "multiple" else ""
@@ -51,13 +48,16 @@ class Lanes.Components.SelectField extends Lanes.Components.MultiSelect
51
48
  readTemplate: ->
52
49
  "<div class='ro-input' name='#{this.field_name}'></div>"
53
50
 
54
-
55
51
  select: (option)->
56
52
  if this.readOnly
57
- this.$el.text( if option then option.code else "" )
53
+ this.$el.text( if option then option[@mappings.title] else "" )
58
54
  else if option
59
55
  id = option.get(@mappings.id)
60
56
  option = this.query("option[value=\"#{id}\"]")
61
- option.selected = true
57
+ if option
58
+ if option.selected
59
+ this.onSelectChange() # force event to fire
60
+ else
61
+ option.selected = true
62
62
  else
63
63
  this.unSelectAll()
@@ -10,3 +10,4 @@
10
10
  //= require ./components/enabled
11
11
  //= require ./screens
12
12
  //= require ./extension/LateLoaded
13
+ //= require ./Boot
@@ -22,7 +22,7 @@
22
22
  <% if !Lanes.config.initial_workspace_screen_id.blank? &&
23
23
  screen = Lanes::Screen[Lanes.config.initial_workspace_screen_id]
24
24
  %>
25
- @import "<%= screen.css.remove(/\.css$/) + "/index.scss" %>"
25
+ @import "<%= screen.css.remove(/\.css$/) + "/index.scss" %>";
26
26
  <% end %>
27
27
 
28
28
  @import "styles/plugins/all"
@@ -1,3 +1,15 @@
1
+ # Hack in support for Function.name for IE
2
+ if Function::name == undefined && Object.defineProperty != undefined
3
+ Object.defineProperty(Function.prototype, 'name', {
4
+ get: ->
5
+ funcNameRegex = /function\s([^(]{1,})\(/;
6
+ results = (funcNameRegex).exec((this).toString())
7
+ return if (results && results.length > 1) then results[1].trim() else ""
8
+
9
+ set: Lanes.emptyFn
10
+ })
11
+
12
+
1
13
  mixinModules = (klass)->
2
14
  Lanes.lib.ModuleSupport.includeInto(klass)
3
15
  if klass::mixins
@@ -86,8 +86,10 @@ class Loader
86
86
  this.body.appendChild(node)
87
87
 
88
88
 
89
- Lanes.lib.Request = (urls...)->
89
+ Lanes.lib.RequestAssets = (urls...)->
90
90
  urls = urls[0] if urls.length == 1 && _.isArray(urls[0])
91
+ for url,i in urls
92
+ urls[i] = Lanes.config.assets_path_prefix.concat( '/', urls[i] )
91
93
  new _.Promise( (resolve,reject)->
92
94
  new Loader(urls, (completed)->
93
95
  failures = _.pick(completed, (status,url)-> !status.success )
@@ -8,5 +8,5 @@ Lanes.namespace = (target, name, block) ->
8
8
  target = target[item] or= {} for item in name.split '.'
9
9
  block( target, top ) if typeof block == 'function'
10
10
 
11
- for ns in ['Components', 'Models.Mixins', 'Views.Mixins', 'Screens', 'Vendor', 'Templates', 'Extensions','lib']
11
+ for ns in ['Components', 'Models.Mixins', 'Views.Mixins', 'Screens.Mixins', 'Vendor', 'Templates', 'Extensions','lib']
12
12
  Lanes.namespace(ns)
@@ -38,13 +38,25 @@ Lanes.u = {
38
38
  else
39
39
  file.namespace[subPath]?[name] || Lanes[subPath]?[name] || Lanes.u.getPath(name)
40
40
 
41
+ #
42
+ findRelative: (name, file)->
43
+ parts = _.map(file.path.slice(0, file.path.length - 1), (comp)->
44
+ _.classify(comp);
45
+ )
46
+ obj = _.reduce( parts, ( ( memo, val )-> return if memo then memo[ val ] else null ), Lanes )
47
+ obj?[name]
48
+
41
49
  # Can be called one of two ways:
42
50
  # With ns being a string, which will attempt to deref it then deref name inside it
43
51
  # or with ns being an object, which will dref name inside it
44
52
  getPath: ( name, ns='Lanes' ) ->
45
53
  return name unless _.isString(name)
46
54
  ns = "Lanes.#{ns}" if _.isString(ns) && !ns.match("Lanes")
47
- distillTypes(name, window) || distillTypes(name, if _.isObject(ns) then ns else distillTypes(ns, window) )
55
+ object = distillTypes(name, window)
56
+ if ! object
57
+ ns=if _.isObject(ns) then ns else distillTypes(ns, window)
58
+ object=distillTypes(name, ns )
59
+ object
48
60
 
49
61
  # Like underscore's results but allows passing
50
62
  # arguments to the function if it's called
@@ -23,7 +23,7 @@ class Lanes.Models.AssocationMap
23
23
  _.extend(options, Lanes.u.resultsFor(model,definition.options))
24
24
  options
25
25
 
26
- # will be called in the scope of the model
26
+ # will be called in the scope of the parent model
27
27
  createModel: (association, name, definition, fk, pk, target_class)->
28
28
  target_class ||= association.getClassFor(name)
29
29
  options = association.getOptions(name,definition,this)
@@ -33,7 +33,7 @@ class Lanes.Models.AssocationMap
33
33
  else
34
34
  target_class.findOrCreate(options)
35
35
 
36
- # will be called in the scope of the model
36
+ # will be called in the scope of the parent model
37
37
  createCollection: (association, name, definition, fk, pk, target_class)->
38
38
  target_class ||= association.getClassFor(name)
39
39
  options = association.getOptions(name,definition,this)
@@ -47,11 +47,13 @@ class Lanes.Models.AssocationMap
47
47
  new Lanes.Models.AssociationCollection(options.models||[],options)
48
48
  # returns the definition for the derived property
49
49
  derivedDefinition: (name,definition)->
50
- createFn = _.partial(
51
- if definition.model then this.createModel else this.createCollection,
52
- this, name, definition, this.fk(name), this.pk(name)
53
- )
54
- { deps: [this.pk(name)], fn: createFn }
50
+ defaultCreator=if definition.model then this.createModel else this.createCollection
51
+ args = [ this, name, definition, this.fk(name), this.pk(name) ]
52
+ createFn = if definition.default
53
+ -> definition.default.apply(this,args) || defaultCreator.apply(this,args)
54
+ else
55
+ defaultCreator
56
+ { deps: [this.pk(name)], fn: _.partial(createFn, args...) }
55
57
 
56
58
 
57
59
  # Sets the assocations for "model"
@@ -68,8 +70,9 @@ class Lanes.Models.AssocationMap
68
70
  association = model[name]
69
71
  association[fn_name]( attributes )
70
72
  # if we're replaceing the model's contents with another, copy the dirty status as well
71
- if association.isModel && value.isModel
72
- association.isDirty = value.isDirty
73
+ if association.isModel
74
+ model.set(this.pk(name), association.id) unless association.isNew
75
+ association.isDirty = value.isDirty if value.isModel
73
76
 
74
77
  pk: (name)->
75
78
  def = @definitions[name]
@@ -1,3 +1,9 @@
1
+ # These prototype properties are also
2
+ # set on any Collections that are created
3
+ PROPERTIES_SHARED_WITH_DEFAULT_COLLECTION = [
4
+ 'cacheDuration'
5
+ ]
6
+
1
7
  # Da Model. Handles all things dataish
2
8
  class BaseModel
3
9
  isModel: true
@@ -89,15 +95,15 @@ class BaseModel
89
95
  if _.isString(options)
90
96
  names.push(options); options={}
91
97
  scope = options.scope || this
92
- needed = this.associations.nonLoaded(this,names)
98
+ needed = this.associations?.nonLoaded(this,names)
93
99
  if _.isEmpty( needed )
94
100
  options.success.call(scope, this ) if options.success
95
101
  options.complete.call(scope,this ) if options.complete
96
- return _.Promise.resolve(this)
102
+ return _.Promise.resolve({record:this,reply:{}})
97
103
  else
98
104
  options['include']=needed
99
105
  this.fetch(options)
100
- .then (req)-> req.record
106
+
101
107
 
102
108
  # Searches the PubSub idenity map for a record of the same type and matching id
103
109
  # If one is found, it will update it with the given attributes and return it
@@ -131,6 +137,7 @@ class BaseModel
131
137
  @fetchById: (id, options={})->
132
138
  record = new this(id: id)
133
139
  record.fetch(options)
140
+ record
134
141
 
135
142
  # Sets the attribute data from a server respose
136
143
  setFromServer: (data,options)->
@@ -160,11 +167,14 @@ class BaseModel
160
167
  # Fetch the model from the server. If the server's representation of the
161
168
  # model differs from its current attributes, they will be overridden,
162
169
  # triggering a `"change"` event.
163
- fetch: (options={}) ->
164
- options = _.clone(options)
170
+ fetch: (original_options={}) ->
171
+ options = _.clone(original_options)
165
172
  handlers = Lanes.Models.Sync.wrapRequest(this,options)
166
- _.extend(options,{limit:1,ignoreUnsaved:true})
167
- this.sync('read', this, options)
173
+ if this.cacheDuration && _.isEmpty(original_options)
174
+ Lanes.Models.ServerCache.fetchRecord(this, options)
175
+ else
176
+ _.extend(options,{limit:1,ignoreUnsaved:true})
177
+ this.sync('read', this, options)
168
178
  handlers.promise
169
179
 
170
180
  @fetch: (options={})->
@@ -200,7 +210,7 @@ class BaseModel
200
210
  # all data is returned. Otherwise only unsaved attributes (and associations)
201
211
  # are returned.
202
212
  dataForSave: (options={})->
203
- if options.saveAll
213
+ if options.saveAll || this.isNew()
204
214
  data = this.getAttributes(props:true, true)
205
215
  else
206
216
  data = this.unsavedAttributes()
@@ -209,7 +219,7 @@ class BaseModel
209
219
  data
210
220
 
211
221
 
212
- # True if the model has "name" as eitehr a prop or session attribute
222
+ # True if the model has "name" as either a prop or session attribute
213
223
  hasAttribute: (name)->
214
224
  !! (this._definition[name] || this._derived[name])
215
225
 
@@ -231,11 +241,23 @@ class BaseModel
231
241
  klass::associations = new Lanes.Models.AssocationMap(klass)
232
242
 
233
243
  @afterExtended: (klass)->
234
- if !klass::abstractClass && !klass.Collection
235
- class DefaultCollection
236
- constructor: -> super
237
- model: klass
238
- klass.Collection = Lanes.Models.Collection.extend(DefaultCollection)
244
+ return if klass::abstractClass
245
+ unless klass.Collection
246
+ @createDefaultCollectionFor(klass)
247
+
248
+ if klass::enums
249
+ klass::enums = new Lanes.Models.EnumMap(klass)
250
+
251
+
252
+ @createDefaultCollectionFor: (klass)->
253
+ name = "#{klass.name}Collection"
254
+ Collection = new Function("return function #{name}(){
255
+ #{name}.__super__.constructor.apply(this, arguments);
256
+ }")()
257
+ Collection::model = klass
258
+ for prop in PROPERTIES_SHARED_WITH_DEFAULT_COLLECTION
259
+ Collection::[prop] = klass::[prop] if klass::[prop]
260
+ klass.Collection = Lanes.Models.Collection.extend(Collection)
239
261
 
240
262
  Lanes.lib.ModuleSupport.includeInto(@)
241
263
  @include Lanes.lib.results
@@ -269,6 +291,10 @@ class BasicModel
269
291
  setFromView: (key,value)->
270
292
  this.set(key,value)
271
293
 
294
+ # True if the model has "name" as eitehr a prop or session attribute
295
+ hasAttribute: (name)->
296
+ !! (this._definition[name] || this._derived[name])
297
+
272
298
  Lanes.Models.BasicModel = State.extend( BasicModel )
273
299
 
274
300
  Lanes.Models.Base = BasicModel.extend( BaseModel )
@@ -49,12 +49,16 @@ class ModelsCollection
49
49
  # current models whith them when the call completes
50
50
  fetch: (options={})->
51
51
  handlers = Lanes.Models.Sync.wrapRequest(this,options)
52
- this.sync('read', this, options)
52
+ if this.cacheDuration
53
+ Lanes.Models.ServerCache.fetchCollection(this, options)
54
+ else
55
+ this.sync('read', this, options)
53
56
  handlers.promise
57
+
54
58
  ensureLoaded: (options={})->
55
59
  handlers = Lanes.Models.Sync.wrapRequest(this,options)
56
60
  if options.force || (!@_isLoaded && !this.length )
57
- this.sync('read', this, options)
61
+ this.fetch(options)
58
62
  else
59
63
  handlers.resolvePromise( record: this, reply: {} )
60
64
  handlers.promise
@@ -0,0 +1,25 @@
1
+ # ------------------------------------------------------------------ #
2
+ # Handles Enum definitions. #
3
+ # It creates a helper methods for each one #
4
+ # and contains utility functions to operate on them #
5
+ # ------------------------------------------------------------------ #
6
+ class Lanes.Models.EnumMap
7
+ constructor: (@klass)->
8
+ @enums = _.clone(@klass::enums)
9
+ for enum_name, def of @enums
10
+ this.defineAccessor(enum_name, _.invert(def))
11
+ for name, id of def
12
+ this.defineEnumHelper(enum_name, name, id)
13
+
14
+ defineAccessor: (name, values)->
15
+ Object.defineProperty(@klass.prototype, "#{name}_value", {
16
+ get: ->
17
+ values[this.get(name)]
18
+ })
19
+
20
+ defineEnumHelper: (enum_name, name, id)->
21
+ Object.defineProperty(@klass.prototype, "is_#{name}", {
22
+ set: Lanes.emptyFn
23
+ get: ->
24
+ this[enum_name] == id
25
+ })
@@ -44,11 +44,11 @@ Lanes.Models.PubSub = {
44
44
  forModel: (model)->
45
45
 
46
46
  add: (model)->
47
- return unless model.isPersistent()
47
+ return unless model.isPersistent?()
48
48
  @types.forModel(model).add(model)
49
49
 
50
50
  remove: (model)->
51
- return unless model && model.isPersistent()
51
+ return unless model && model.isPersistent?()
52
52
  @types.forModel(model).remove(model)
53
53
 
54
54
  instanceFor: ( model_klass, id )->
@@ -177,7 +177,7 @@ class Lanes.Models.Query extends Lanes.Models.Base
177
177
 
178
178
  loadSingle: (code,options)->
179
179
  options.query = {}
180
- options.query[ @initialField ] = code
180
+ options.query[ @initialField.id ] = code
181
181
  @modelClass.fetch(options)
182
182
 
183
183
  defaultField: ->
@@ -0,0 +1,63 @@
1
+ class CacheEntry
2
+
3
+ constructor: (@collection, @options, @key)->
4
+ @duration = Lanes.Vendor.Moment.duration(@collection.cacheDuration...)
5
+ _.bindAll(this,'setFromServer')
6
+ @successCallback = @options.success
7
+ @options.success = this.setFromServer
8
+ Lanes.Models.Sync.perform('read', this, @options)
9
+
10
+ url: ->
11
+ _.result(@collection,'url')
12
+
13
+ setFromServer: (reply, status, req)->
14
+ if reply?.data?
15
+ Lanes.Models.ServerCache.store(@key, reply.data, @duration.asMilliseconds())
16
+ @successCallback?.apply(@options.scope, arguments)
17
+
18
+ # # needed to
19
+ # trigger: (type, model, xhr, options)->
20
+ # @collection.trigger(type, @collection, xhr, options)
21
+
22
+
23
+ Lanes.Models.ServerCache = {
24
+ CACHE: {}
25
+
26
+ store: (key, data, ms)->
27
+ this.CACHE[key] = data
28
+ setTimeout(->
29
+ delete Lanes.Models.ServerCache.CACHE[key]
30
+ , ms)
31
+
32
+ computeCacheKey: (collection, options)->
33
+ url = _.result(collection, "url")
34
+ query = {}
35
+ for key, value of options
36
+ query[ Lanes.Models.Sync.paramsMap[key] ] = value if Lanes.Models.Sync.paramsMap[key]
37
+ url + Lanes.$.param(query)
38
+
39
+ fetchRecord: (record, options)->
40
+ key = record.urlRoot()
41
+
42
+ if data_set=this.CACHE[key]
43
+ found = false
44
+ for data in data_set
45
+ if record.id == data.id
46
+ found = data
47
+ break
48
+ if data_set && found
49
+ options.success.call(options.scope, {data: found}, "sucess", {})
50
+ else
51
+ this.sync('read', this, options)
52
+
53
+ false
54
+
55
+ fetchCollection: (collection, options)->
56
+ key = this.computeCacheKey(collection, options)
57
+ if this.CACHE[key]
58
+ collection.setFromServer(this.CACHE[key], options)
59
+ options.success.call(options.scope, {data: this.CACHE[key]}, "sucess", {})
60
+ else
61
+ new CacheEntry(collection,options, key)
62
+
63
+ }
@@ -15,19 +15,17 @@ getValue = (object, prop)->
15
15
 
16
16
 
17
17
 
18
-
19
- paramsMap = {
20
- fields : 'f'
21
- with : 'w'
22
- query : 'q'
23
- include : 'i'
24
- order : 'o'
25
- limit : 'l'
26
- start : 's'
27
- format : 'df'
28
- }
29
-
30
18
  Lanes.Models.Sync = {
19
+ paramsMap:
20
+ fields : 'f'
21
+ with : 'w'
22
+ query : 'q'
23
+ include : 'i'
24
+ order : 'o'
25
+ limit : 'l'
26
+ start : 's'
27
+ format : 'df'
28
+
31
29
  urlError: ->
32
30
  throw new Error('A "url" property or function must be specified')
33
31
 
@@ -65,7 +63,7 @@ Lanes.Models.Sync = {
65
63
  perform: (method, model, options={})->
66
64
  query = {}
67
65
  for key, value of options
68
- query[ paramsMap[key] ] = value if paramsMap[key]
66
+ query[ this.paramsMap[key] ] = value if this.paramsMap[key]
69
67
 
70
68
  # Default JSON-request options.
71
69
  params = {
@@ -1,6 +1,8 @@
1
1
  //= require_self
2
2
  //= require ./Sync
3
- //= require ./ModelAssociations
3
+ //= require ./ServerCache
4
+ //= require ./AssociationMap
5
+ //= require ./EnumMap
4
6
  //= require ./Base
5
7
  //= require ./Collection
6
8
  //= require_tree ./mixins
@@ -10,7 +10,7 @@ class ScreenBase
10
10
  mixins:[
11
11
  Lanes.Screens.ChangeListener
12
12
  ]
13
-
13
+ formBindings: true
14
14
  reset: Lanes.emptyFn
15
15
 
16
16
  templateName: 'layout'
@@ -66,7 +66,7 @@ class ScreenDefinition extends Lanes.Models.BasicModel
66
66
  me=this
67
67
  return new _.Promise( (resolve,reject)->
68
68
  me.loading=true
69
- Lanes.lib.Request(me.assets)
69
+ Lanes.lib.RequestAssets(me.assets...)
70
70
  .then ->
71
71
  me.loading=false
72
72
  me.viewModel = Lanes.u.getPath(me.view, me.FILE?['Views'])