lanes 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +0 -1
- data/client/lanes/{screens/Instance.coffee → Boot.coffee} +5 -5
- data/client/lanes/Config.coffee +2 -0
- data/client/lanes/components/multi-select/MultiSelect.coffee +20 -6
- data/client/lanes/components/popover/PopOver.coffee +5 -2
- data/client/lanes/components/record-finder/RecordFinder.coffee +12 -7
- data/client/lanes/components/record-finder/config.json +1 -1
- data/client/lanes/components/select-field/SelectField.coffee +8 -8
- data/client/lanes/index.js +1 -0
- data/client/lanes/index.scss.erb +1 -1
- data/client/lanes/lib/MakeBaseClass.coffee +12 -0
- data/client/lanes/lib/loader.coffee +3 -1
- data/client/lanes/lib/namespace.coffee +1 -1
- data/client/lanes/lib/utilFunctions.coffee +13 -1
- data/client/lanes/models/{ModelAssociations.coffee → AssociationMap.coffee} +12 -9
- data/client/lanes/models/Base.coffee +40 -14
- data/client/lanes/models/Collection.coffee +6 -2
- data/client/lanes/models/EnumMap.coffee +25 -0
- data/client/lanes/models/PubSub.coffee +2 -2
- data/client/lanes/models/Query.coffee +1 -1
- data/client/lanes/models/ServerCache.coffee +63 -0
- data/client/lanes/models/Sync.coffee +11 -13
- data/client/lanes/models/index.js +3 -1
- data/client/lanes/screens/Base.coffee +1 -1
- data/client/lanes/screens/Definitions.coffee +1 -1
- data/client/lanes/screens/index.js +1 -1
- data/client/lanes/screens/mixins/Editing.coffee +38 -0
- data/client/lanes/screens/mixins/index.js +1 -0
- data/client/lanes/styles/plugins/overlay.scss +7 -0
- data/client/lanes/testing/BeforeEach.coffee +62 -0
- data/client/lanes/testing/ModelSaver.coffee +5 -6
- data/client/lanes/testing/TestModels.coffee +23 -0
- data/client/lanes/testing/index.js +3 -1
- data/client/lanes/vendor/packaged.js +5860 -26
- data/client/lanes/views/Base.coffee +20 -20
- data/client/lanes/views/FormBindings.coffee +4 -2
- data/client/lanes/views/RenderContext.coffee +2 -2
- data/client/lanes/workspace/Layout.coffee +6 -2
- data/client/lanes/workspace/Navbar.coffee +1 -1
- data/client/lanes/workspace/Pages.coffee +1 -1
- data/client/lanes/workspace/navbar.html +3 -1
- data/client/lanes/workspace/styles/header.scss +3 -1
- data/lib/lanes/access/authentication_provider.rb +2 -2
- data/lib/lanes/access/config/routes.rb +1 -1
- data/lib/lanes/access/extension.rb +1 -4
- data/lib/lanes/access/role.rb +20 -15
- data/lib/lanes/access/test_fixture_extensions.rb +20 -0
- data/lib/lanes/access.rb +3 -3
- data/lib/lanes/api/request_wrapper.rb +3 -2
- data/lib/lanes/api/sprockets_extension.rb +1 -1
- data/lib/lanes/api/test_specs.rb +1 -1
- data/lib/lanes/capistrano.rb +18 -6
- data/lib/lanes/command/app.rb +1 -1
- data/lib/lanes/command/generate_model.rb +6 -0
- data/lib/lanes/command/generate_view.rb +7 -6
- data/lib/lanes/command/named_command.rb +1 -1
- data/lib/lanes/concerns/all.rb +1 -0
- data/lib/lanes/concerns/code_identifier.rb +43 -0
- data/lib/lanes/concerns/set_attribute_data.rb +0 -8
- data/lib/lanes/configuration.rb +1 -1
- data/lib/lanes/db.rb +11 -9
- data/lib/lanes/extension.rb +1 -0
- data/lib/lanes/guard_tasks.rb +1 -1
- data/lib/lanes/model.rb +1 -0
- data/lib/lanes/rake_tasks.rb +0 -2
- data/lib/lanes/screen.rb +1 -4
- data/lib/lanes/spec_helper.rb +5 -2
- data/lib/lanes/version.rb +1 -1
- data/npm-build/package.json +1 -0
- data/npm-build/template.js +1 -0
- data/spec/command-reference-files/initial/Gemfile +4 -1
- data/spec/command-reference-files/initial/client/appy-app/Extension.coffee +11 -1
- data/spec/command-reference-files/initial/config/lanes.rb +4 -1
- data/spec/command-reference-files/initial/lib/appy-app/extension.rb +12 -1
- data/spec/command-reference-files/initial/lib/appy-app.rb +0 -1
- data/spec/command-reference-files/model/lib/appy-app/model.rb +12 -0
- data/spec/command-reference-files/model/lib/appy-app.rb +11 -0
- data/spec/command-reference-files/view/client/appy-app/views/BigView.coffee +1 -0
- data/spec/lanes/helpers/lanes-helpers.coffee +0 -79
- data/spec/lanes/models/{ModelAssociationsSpec.coffee → AssociationMapSpec.coffee} +0 -0
- data/spec/lanes/models/BaseSpec.coffee +25 -12
- data/spec/lanes/models/CollectionSpec.coffee +20 -0
- data/spec/lanes/models/EnumMapSpec.coffee +26 -0
- data/spec/server/command_spec.rb +1 -1
- data/templates/Gemfile +4 -1
- data/templates/client/Extension.coffee +11 -1
- data/templates/client/views/View.coffee +2 -1
- data/templates/config/lanes.rb +4 -1
- data/templates/lib/namespace/extension.rb +12 -1
- data/templates/lib/namespace.rb +0 -1
- data/templates/spec/client/views/ViewSpec.coffee +1 -1
- metadata +16 -6
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ed648a6329d1d1b0c9ee0e5df25d1c184b5f5e0
|
4
|
+
data.tar.gz: 023a41ef58e894aa882ae712edb9a77b12d57d73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6cfb3a4c38ec8dd175c17047aafc03ced98e6d30ce04e48f5bd5b2550aa88521cad0ac12252a00dfdaacff9c606bd68f5d3bdccf8b5882e7b396e16c2f189e6
|
7
|
+
data.tar.gz: 8befe413cd47f78796a50d344ffcd89e7021ad7d4541889a67d483b0a02c41bda1b6335de7c3263a00d30124b68dc1cd091e793ceacb9c030ed695943da95468
|
data/Rakefile
CHANGED
@@ -1,8 +1,4 @@
|
|
1
|
-
|
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);
|
data/client/lanes/Config.coffee
CHANGED
@@ -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: '
|
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
|
-
_.
|
50
|
-
|
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
|
52
|
-
this.
|
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
|
40
|
+
if _.isEmpty(@withAssociations)
|
41
|
+
record.fetch()
|
42
|
+
else
|
43
|
+
record.withAssociations(@withAssociations...)
|
41
44
|
else
|
42
45
|
null
|
43
|
-
).then( (
|
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
|
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
|
-
|
54
|
-
code = this.$(ev.target).val()
|
57
|
+
loadCode: (code)->
|
55
58
|
@recordQuery.loadSingle(code.toUpperCase(),{ include: @withAssociations })
|
56
|
-
.then( (reply)=>
|
59
|
+
.then( (reply)=>
|
60
|
+
this.$el.trigger("display-record", reply.record) if reply.record
|
61
|
+
)
|
@@ -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
|
-
|
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.
|
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
|
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()
|
data/client/lanes/index.js
CHANGED
data/client/lanes/index.scss.erb
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
72
|
-
association.
|
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
|
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
|
-
|
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: (
|
164
|
-
options = _.clone(
|
170
|
+
fetch: (original_options={}) ->
|
171
|
+
options = _.clone(original_options)
|
165
172
|
handlers = Lanes.Models.Sync.wrapRequest(this,options)
|
166
|
-
_.
|
167
|
-
|
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
|
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
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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.
|
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.
|
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 = {
|
@@ -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.
|
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'])
|