lanes 0.1.8 → 0.1.9

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 (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'])