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