lanes 0.5.0 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/client/fonts/fontawesome-webfont.woff +0 -0
  3. data/client/fonts/fontawesome-webfont.woff2 +0 -0
  4. data/client/lanes/Boot.cjsx +1 -6
  5. data/client/lanes/components/grid/Body.cjsx +14 -1
  6. data/client/lanes/components/grid/Grid.cjsx +20 -19
  7. data/client/lanes/components/grid/Toolbar.cjsx +7 -4
  8. data/client/lanes/components/grid/editors.scss +1 -1
  9. data/client/lanes/components/grid/styles.scss +16 -5
  10. data/client/lanes/components/modal/Modal.cjsx +9 -11
  11. data/client/lanes/components/record-finder/RecordFinder.cjsx +2 -2
  12. data/client/lanes/components/record-finder/styles.scss +7 -5
  13. data/client/lanes/components/shared/FormGroup.cjsx +1 -1
  14. data/client/lanes/components/shared/Icon.cjsx +3 -2
  15. data/client/lanes/components/shared/Input.cjsx +1 -0
  16. data/client/lanes/components/shared/PanelHeader.cjsx +8 -0
  17. data/client/lanes/components/shared/fields.scss +5 -4
  18. data/client/lanes/components/shared/styles.scss +18 -0
  19. data/client/lanes/index.js +0 -1
  20. data/client/lanes/{plugins → lib}/ResizeSensor.js +0 -0
  21. data/client/lanes/lib/all.js +13 -0
  22. data/client/lanes/lib/dom.coffee +1 -1
  23. data/client/lanes/lib/format.coffee +2 -2
  24. data/client/lanes/lib/index.js.erb +1 -13
  25. data/client/lanes/lib/loader.coffee +2 -2
  26. data/client/lanes/lib/utilFunctions.coffee +12 -0
  27. data/client/lanes/models/AssociationMap.coffee +27 -18
  28. data/client/lanes/models/Base.coffee +12 -4
  29. data/client/lanes/models/Collection.coffee +5 -3
  30. data/client/lanes/models/PubSub.coffee +1 -1
  31. data/client/lanes/models/Query.coffee +0 -1
  32. data/client/lanes/models/State.coffee +5 -1
  33. data/client/lanes/models/Sync.coffee +5 -9
  34. data/client/lanes/models/query/ArrayResult.coffee +27 -7
  35. data/client/lanes/models/query/Result.coffee +2 -0
  36. data/client/lanes/react/Component.coffee +3 -3
  37. data/client/lanes/react/Viewport.coffee +17 -5
  38. data/client/lanes/react/mixins/Access.coffee +2 -2
  39. data/client/lanes/react/mixins/FieldErrors.coffee +3 -3
  40. data/client/lanes/remote/BaseClasses.coffee +0 -0
  41. data/client/lanes/remote/api.coffee +8 -0
  42. data/client/lanes/styles/fonts/_icons.scss +139 -2
  43. data/client/lanes/styles/fonts/_variables.scss +142 -4
  44. data/client/lanes/styles/global/mixins.scss +5 -0
  45. data/client/lanes/styles/global/styles.scss +5 -2
  46. data/client/lanes/styles/global.scss +1 -0
  47. data/client/lanes/vendor/base.js.erb +4 -2
  48. data/client/lanes/vendor/development/calendar.js +65 -65
  49. data/client/lanes/vendor/development/commons.js +34530 -34719
  50. data/client/lanes/vendor/development/data.js +30832 -0
  51. data/client/lanes/vendor/development/helpers.js +26 -26
  52. data/client/lanes/vendor/development/toggle.js +19 -19
  53. data/client/lanes/vendor/development/ui.js +22568 -0
  54. data/client/lanes/vendor/development/widgets.js +362 -362
  55. data/client/lanes/vendor/production/calendar.js +65 -65
  56. data/client/lanes/vendor/production/commons.js +34360 -34549
  57. data/client/lanes/vendor/production/data.js +30829 -0
  58. data/client/lanes/vendor/production/toggle.js +19 -19
  59. data/client/lanes/vendor/production/ui.js +22564 -0
  60. data/client/lanes/vendor/production/widgets.js +362 -362
  61. data/client/lanes/vendor/{production/base.js → standalone/index.js} +43836 -53602
  62. data/client/lanes/workspace/Modal.cjsx +1 -1
  63. data/client/lanes/workspace/styles/header.scss +1 -0
  64. data/client/lanes/workspace/styles/layout.scss +12 -0
  65. data/lanes.gemspec +1 -0
  66. data/lib/lanes/access/authentication_provider.rb +4 -3
  67. data/lib/lanes/api/coffeescript_processor.rb +9 -3
  68. data/lib/lanes/api/{controller.rb → controller_base.rb} +3 -35
  69. data/lib/lanes/api/formatted_reply.rb +2 -2
  70. data/lib/lanes/api/generic_controller.rb +42 -0
  71. data/lib/lanes/api/helper_methods.rb +1 -1
  72. data/lib/lanes/api/null_authentication_provider.rb +3 -0
  73. data/lib/lanes/api/request_wrapper.rb +32 -18
  74. data/lib/lanes/api/root.rb +25 -3
  75. data/lib/lanes/api/routing.rb +41 -22
  76. data/lib/lanes/api/sprockets_extension.rb +3 -1
  77. data/lib/lanes/api.rb +2 -1
  78. data/lib/lanes/configuration.rb +0 -1
  79. data/lib/lanes/extension/definition.rb +5 -1
  80. data/lib/lanes/hot_reload_plugin.rb +0 -1
  81. data/lib/lanes/version.rb +1 -1
  82. data/npm-build/build +3 -0
  83. data/npm-build/{base.js → data.js} +16 -16
  84. data/npm-build/package.json +3 -1
  85. data/npm-build/standalone.js +4 -0
  86. data/npm-build/ui.js +8 -0
  87. data/npm-build/webpack-standalone.config.js +16 -0
  88. data/npm-build/webpack.config.js +4 -3
  89. data/templates/config/lanes.rb +3 -3
  90. metadata +34 -10
  91. data/client/lanes/lib/noConflict.coffee +0 -15
  92. data/client/lanes/plugins/index.js +0 -1
  93. data/client/lanes/vendor/development/base.js +0 -61239
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 98fcf2619dab60aa9576d6e71d67be4385a56ef6
4
- data.tar.gz: a8d42b6cec864bc69e333bff826689378551c033
3
+ metadata.gz: 423cf2958270fce5f4d1fb05fbba10fd56afbcd8
4
+ data.tar.gz: e5a90b6559ebbadf4c9eb63833d0aa59e1b4d017
5
5
  SHA512:
6
- metadata.gz: 2e3d155815b2991ab8221bf0faca2e44005e44bcebe4900d9273ed7913d830a971b647faf9ba1002365de6113610a13a9286e876679c572fe5037fbe9f2ccd96
7
- data.tar.gz: 7e51bfeecd2a829219816d435f7a636829d0e90f388ec85ff961cdbc9e5e2cbc8ecd4485ec3c0c04a240fdd5d84b7557e61130ed471b3487c6b002d6b25cb237
6
+ metadata.gz: 7f24e95dc672cdb9d18f6937fac870a5d216046739771d534291997369ab4488c807c92a7ad82151428b310466970085e69fd6f7c48caffd94f0a5471baa97cc
7
+ data.tar.gz: f680982d7d8767bfc225b61bfd38a97e6ab11185d426e60a6c3fad7319479d7433f21bc2a2b843d24d00a32445a9e84c49f611e5cf515f06531216237cd497c6
Binary file
Binary file
@@ -1,9 +1,4 @@
1
1
  Lanes.renderScreenTo = (selector, options) ->
2
2
  Lanes.config.bootstrap(options)
3
3
  document.addEventListener "DOMContentLoaded", ->
4
- viewport = new Lanes.React.Viewport(selector: selector)
5
- Lanes.Models.PubSub.initialize()
6
- Lanes.Extensions.fireOnInitialized(viewport)
7
- viewport.renderRoot()
8
- Lanes.Extensions.fireOnAvailable(viewport)
9
- viewport.onBoot()
4
+ viewport = new Lanes.React.Viewport({selector, options})
@@ -1,3 +1,12 @@
1
+ class AllRows extends Lanes.React.BaseComponent
2
+ render: ->
3
+ len = @props.length
4
+ rows = []
5
+ for i in [0...len - 1] by 1
6
+ rows.push @props.itemRenderer(i, i)
7
+ <div>{rows}</div>
8
+
9
+
1
10
  class Lanes.Components.Grid.Body extends Lanes.React.BaseComponent
2
11
 
3
12
  propTypes:
@@ -72,7 +81,10 @@ class Lanes.Components.Grid.Body extends Lanes.React.BaseComponent
72
81
  {fields}
73
82
  </div>
74
83
 
84
+
75
85
  render: ->
86
+ Rows = if @props.renderCompleteResults then AllRows else Lanes.Vendor.List
87
+
76
88
  <div className={_.classnames('grid-body', 'is-editing': @props.editing)}>
77
89
 
78
90
  <LC.NetworkActivityOverlay model={@props.query} />
@@ -84,7 +96,8 @@ class Lanes.Components.Grid.Body extends Lanes.React.BaseComponent
84
96
  onCancel={@props.onEditCancel}
85
97
  editing={@props.editing} />
86
98
 
87
- <Lanes.Vendor.List
99
+
100
+ <Rows
88
101
  useTranslate3d
89
102
  isEditing={!!@props.editing}
90
103
  itemRenderer={@renderRow}
@@ -10,23 +10,6 @@
10
10
 
11
11
  class Lanes.Components.Grid extends Lanes.React.Component
12
12
 
13
- mixins: [
14
- Lanes.React.Mixins.MonitorSize
15
- ]
16
-
17
- dataObjects:
18
- query: 'props'
19
-
20
- bindDataEvents:
21
- query: 'load change sort'
22
-
23
- getInitialState: ->
24
- {}
25
-
26
- componentWillReceiveProps: (nextProps) ->
27
- if nextProps.autoLoadQuery and nextProps.query isnt @props.query
28
- nextProps.query.ensureLoaded()
29
-
30
13
  propTypes:
31
14
  query: React.PropTypes.instanceOf(Lanes.Models.Query).isRequired
32
15
  width: React.PropTypes.number
@@ -40,17 +23,35 @@ class Lanes.Components.Grid extends Lanes.React.Component
40
23
  onColumnClick: React.PropTypes.func
41
24
  onSelectionChange: React.PropTypes.func
42
25
  autoLoadQuery: React.PropTypes.bool
26
+ renderCompleteResults: React.PropTypes.bool
43
27
  toolbarChildren: React.PropTypes.oneOfType([
44
28
  React.PropTypes.element,
45
29
  React.PropTypes.arrayOf(React.PropTypes.element)
46
30
  ])
47
31
 
48
- getDefaultProps: ->
49
- editorProps: {}, autoLoadQuery: true
32
+ mixins: [
33
+ Lanes.React.Mixins.MonitorSize
34
+ ]
35
+
36
+ dataObjects:
37
+ query: 'props'
38
+
39
+ bindDataEvents:
40
+ query: 'load change sort'
41
+
42
+ componentWillReceiveProps: (nextProps) ->
43
+ if nextProps.autoLoadQuery and nextProps.query isnt @props.query
44
+ nextProps.query.ensureLoaded()
50
45
 
51
46
  componentWillMount: ->
52
47
  @query.ensureLoaded() if @props.autoLoadQuery
53
48
 
49
+ getInitialState: ->
50
+ {}
51
+
52
+ getDefaultProps: ->
53
+ editorProps: {}, autoLoadQuery: true
54
+
54
55
  onSortChange: (sortInfo) ->
55
56
  for sortConfig in sortInfo
56
57
  sort = @query.fields.at(sortConfig.name).sortBy
@@ -1,7 +1,7 @@
1
1
  class Lanes.Components.Grid.Toolbar extends Lanes.React.BaseComponent
2
2
 
3
3
  propTypes:
4
- addRecord: React.PropTypes.func
4
+ onAddRecord: React.PropTypes.func
5
5
  startEdit: React.PropTypes.func
6
6
  toolbarChildren: React.PropTypes.oneOfType([
7
7
  React.PropTypes.element,
@@ -13,16 +13,19 @@ class Lanes.Components.Grid.Toolbar extends Lanes.React.BaseComponent
13
13
  @props.startEdit(0)
14
14
 
15
15
  AddButton: ->
16
- return null unless @props.allowCreate
16
+ return null unless @props.onAddRecord and @props.allowCreate
17
17
  <BS.Button className="navbar-btn add-row pull-right"
18
18
  onClick={@onAddRecord} bsSize='small'
19
19
  >
20
20
  <LC.Icon type="plus" />Add Row
21
21
  </BS.Button>
22
22
 
23
+
24
+ shouldDisplay: ->
25
+ not ( ( false == @props.commands?.isEditing() or not @props.allowCreate ) and not @props.toolbarChildren )
26
+
23
27
  render: ->
24
- if ( ( false == @props.commands?.isEditing() or not @props.allowCreate ) and not @props.toolbarChildren )
25
- return null
28
+ return null unless @shouldDisplay()
26
29
 
27
30
  props = _.extend {}, @props,
28
31
  <Lanes.Components.Grid.Toolbar key="toolbar" {...props} />
@@ -5,7 +5,7 @@ $editor-border-color: #a8a8a8;
5
5
  $editor-border: 1px solid $editor-border-color;
6
6
 
7
7
  .toolbar {
8
- .add-row { margin-right: 10px; }
8
+ .pull-right { margin-right: 10px; }
9
9
  }
10
10
 
11
11
  .editor {
@@ -9,7 +9,6 @@
9
9
  flex-direction: column;
10
10
  flex: 1;
11
11
 
12
-
13
12
  // styles shared between header and row
14
13
  .header, .r {
15
14
  display: flex;
@@ -17,13 +16,12 @@
17
16
  min-height: 40px;
18
17
  display: flex;
19
18
  flex-direction: row;
19
+ page-break-inside: avoid;
20
20
  .c {
21
21
  padding: $table-cell-padding;
22
22
  line-height: $line-height-base;
23
-
24
23
  display: flex;
25
24
  flex-direction: row;
26
-
27
25
  &.center { justify-content: center; }
28
26
  &.right { justify-content: flex-end; }
29
27
 
@@ -54,6 +52,11 @@
54
52
  right: -5px;
55
53
  color: lightgray;
56
54
  }
55
+ &:not(.asc):not(.desc) {
56
+ &:after {
57
+ @include hidden-print;
58
+ }
59
+ }
57
60
  &.asc:after {
58
61
  color: gray;
59
62
  content: $fa-var-sort-asc;
@@ -65,13 +68,21 @@
65
68
  }
66
69
  }
67
70
  }
68
-
71
+ .r {
72
+ margin-right: 10px;
73
+ /* page-break-inside: avoid; */
74
+ break-inside: avoid-page;
75
+ }
69
76
  .grid-body {
70
77
  border-top: 1px solid $table-border-color;
71
78
  overflow: auto;
72
79
  position: relative; // for absolutely positioned row editor
73
80
  flex-grow: 1;
74
- height: 1px;
81
+ height: 1px; // panel needs height to force scroll
82
+ @media print {
83
+ overflow: visible;
84
+ height: initial;
85
+ }
75
86
  .r:nth-child(odd) {
76
87
  background-color: $table-bg-accent;
77
88
  }
@@ -25,15 +25,15 @@ class Lanes.Components.Modal extends Lanes.React.Component
25
25
  getInitialState: ->
26
26
  show: false
27
27
 
28
- onOkButton: -> @state.onOk?(this)
29
- onCancelButton: -> @state.onCancel?(this)
30
- onButton: (btn) ->
28
+ onOkButton: (ev) -> @state.onOk?(this, ev)
29
+ onCancelButton: (ev) -> @state.onCancel?(this, ev)
30
+ onButton: (ev, btn) ->
31
31
  @selected = btn
32
- @_hide() if @state.autoHide
33
32
  if btn.eventKey is 'ok'
34
- @onOkButton()
33
+ @onOkButton(ev)
35
34
  else
36
- @onCancelButton()
35
+ @onCancelButton(ev)
36
+ @_hide() if @state.autoHide and ev.defaultPrevented isnt true
37
37
 
38
38
  componentWillReceiveProps: (nextProps) ->
39
39
  @replaceState(nextProps)
@@ -46,9 +46,7 @@ class Lanes.Components.Modal extends Lanes.React.Component
46
46
  @context.viewport.modalProps.show = true
47
47
  @setState(show: true)
48
48
 
49
- hide: ->
50
- @_hide()
51
- @state.onCancel?()
49
+ hide: -> @_hide()
52
50
 
53
51
  render: ->
54
52
  return null unless @state.show
@@ -58,7 +56,7 @@ class Lanes.Components.Modal extends Lanes.React.Component
58
56
  button.eventKey ||= (button.key or button.title).toLowerCase()
59
57
  <BS.Button key={button.title}
60
58
  bsStyle={button.style || 'default'} className={name}
61
- onClick={_.partial(@onButton, button)}>{button.title}</BS.Button>
59
+ onClick={_.partial(@onButton, _, button)}>{button.title}</BS.Button>
62
60
 
63
61
  cls = _.classnames('lanes-modal', @state.className, @context.uistate?.layout_size)
64
62
  Body = @state.body
@@ -68,7 +66,7 @@ class Lanes.Components.Modal extends Lanes.React.Component
68
66
  </BS.Modal.Header>
69
67
 
70
68
  <BS.Modal.Body style={maxHeight: @context.viewport.height - 250}>
71
- <Body modal={@} />
69
+ <Body ref="body" {...@props} modal={@} />
72
70
  </BS.Modal.Body>
73
71
 
74
72
  <BS.Modal.Footer>{buttons}</BS.Modal.Footer>
@@ -49,7 +49,7 @@ class Lanes.Components.RecordFinder extends Lanes.React.Component
49
49
 
50
50
  getValue: (ev) ->
51
51
  value = if @props.parentModel
52
- @props.parentModel[@props.associationName][@props.name]
52
+ @props.parentModel[@props.associationName]?[@props.name]
53
53
  else
54
54
  @props.model[@props.name]
55
55
  value or ''
@@ -59,7 +59,7 @@ class Lanes.Components.RecordFinder extends Lanes.React.Component
59
59
  # editOnly writable
60
60
  model = @props.parentModel or @props.model
61
61
 
62
- <BS.InputGroup>
62
+ <BS.InputGroup className="record-finder">
63
63
 
64
64
  <BS.FormControl
65
65
  {...props} {...handlers}
@@ -4,13 +4,15 @@
4
4
  }
5
5
 
6
6
 
7
- .record-finder-query-string {
8
- text-transform: uppercase;
9
- }
10
-
11
7
  .record-finder {
12
8
  .form-control {
13
-
9
+ @media print {
10
+ border-bottom-right-radius: $border-radius-base;
11
+ border-top-right-radius: $border-radius-base;
12
+ }
13
+ }
14
+ .input-group-btn {
15
+ @include hidden-print;
14
16
  }
15
17
  }
16
18
 
@@ -26,7 +26,7 @@ class Lanes.Components.FormGroup extends Lanes.React.Component
26
26
  })
27
27
  <BS.Col {...colProps} className={className}>
28
28
  <BS.FormGroup className={valueClassNames}>
29
- <LC.ControlLabel {...@props} />
29
+ <BS.ControlLabel>{@props.label}</BS.ControlLabel>
30
30
  {@props.children}
31
31
  </BS.FormGroup>
32
32
  </BS.Col>
@@ -1,4 +1,4 @@
1
- class Lanes.Components.Icon extends Lanes.React.Component
1
+ class Lanes.Components.Icon extends Lanes.React.BaseComponent
2
2
 
3
3
  propTypes:
4
4
  type: React.PropTypes.string.isRequired
@@ -7,7 +7,8 @@ class Lanes.Components.Icon extends Lanes.React.Component
7
7
  render: ->
8
8
 
9
9
  classes = _.classnames 'icon', "icon-#{@props.type}", @props.className,
10
- 'cursor-pointer' : @props.onClick,
10
+ 'non-printable': @props.noPrint,
11
+ 'with-action' : @props.onClick,
11
12
  "icon-#{@props.size}" : @props.size,
12
13
  'icon-pulse' : @props.animated
13
14
  'flush' : @props.flush
@@ -6,6 +6,7 @@ class Lanes.Components.Input extends Lanes.React.Component
6
6
 
7
7
  renderInputField: (props, handlers) ->
8
8
  <BS.FormControl
9
+ type={@props.type or 'text'}
9
10
  {...props}
10
11
  {...handlers}
11
12
  onChange={@fieldMixinSetValue}
@@ -0,0 +1,8 @@
1
+ class Lanes.Components.PanelHeader extends Lanes.React.BaseComponent
2
+
3
+ render: ->
4
+ <div className="lanes-panel-heading">
5
+ <h3 className="panel-title">{@props.title}</h3>
6
+ <div className="spacer" />
7
+ {@props.children}
8
+ </div>
@@ -20,20 +20,16 @@ $lanes-field-margin: 6px;
20
20
  }
21
21
 
22
22
  }
23
-
24
23
  &.align-right{
25
- text-align: right;
26
24
  .control-label, .value, .rw-widget input {text-align: right; }
27
25
  }
28
26
  &.align-center{
29
- text-align: center;
30
27
  .control-label, .value, .rw-widget input { text-align: center; }
31
28
  }
32
29
 
33
30
  &.select {
34
31
  .form-control-feedback { right: 30px; }
35
32
  }
36
-
37
33
  .form-group {
38
34
  background-color: rgba(255, 255, 255, 1);
39
35
  transition: background-color 1000ms linear;
@@ -49,4 +45,9 @@ $lanes-field-margin: 6px;
49
45
  background-color: darken($color-rgba, 20%);
50
46
  }
51
47
  }
48
+
49
+ .rw-datetimepicker {
50
+ &.rw-has-both { padding-right: 0; }
51
+ .rw-select { @include hidden-print; }
52
+ }
52
53
  }
@@ -44,3 +44,21 @@
44
44
  justify-content: center;
45
45
  .progress { width: 100%; }
46
46
  }
47
+
48
+ .icon {
49
+ &.with-action {
50
+ cursor: pointer;
51
+ &:hover{ text-shadow: 1px 2px 2px #666666; }
52
+ }
53
+ }
54
+
55
+ .lanes-panel-heading {
56
+ display: flex;
57
+ align-items: center;
58
+ .spacer {
59
+ flex: 1;
60
+ }
61
+ > *:not(.panel-title){
62
+ margin-left: 0.5rem;
63
+ }
64
+ }
@@ -2,7 +2,6 @@
2
2
  //= require ./extension
3
3
  //= require ./models
4
4
  //= require ./Config
5
- //= require ./plugins
6
5
  //= require ./react
7
6
  //= require ./components
8
7
  //= require ./screens
File without changes
@@ -0,0 +1,13 @@
1
+ //= require ./namespace
2
+ //= require ./utilFunctions
3
+ //= require ./objToParam
4
+ //= require ./ModuleSupport
5
+ //= require ./loader
6
+ //= require ./MakeBaseClass
7
+ //= require ./el
8
+ //= require ./dom-polyfills
9
+ //= require ./dom
10
+ //= require ./format
11
+ //= require ./promise_helpers
12
+ //= require ./results
13
+ //= require ./ResizeSensor
@@ -66,7 +66,7 @@ _.dom = (unknown, query) ->
66
66
  throw new TypeError("Selector / DOM node is not present")
67
67
  else if _.isElement(unknown)
68
68
  unknown
69
- else if unknown.isReactComponent
69
+ else if unknown.isReactComponent or unknown.render
70
70
  Lanes.Vendor.ReactDOM.findDOMNode(unknown)
71
71
  else if unknown.nodeType is 9 # body tag
72
72
  unknown
@@ -1,9 +1,9 @@
1
1
  Lanes.u.format ||= {}
2
2
 
3
- Lanes.u.format.shartDate = (d) ->
3
+ Lanes.u.format.shortDate = (d) ->
4
4
  _.moment(d).format('L')
5
5
 
6
- Lanes.u.format.shartDateTime = (d) ->
6
+ Lanes.u.format.shortDateTime = (d) ->
7
7
  _.moment(d).format('lll')
8
8
 
9
9
  Lanes.u.format.currency = (v) ->
@@ -1,16 +1,4 @@
1
- //= require ./noConflict
2
- //= require ./namespace
3
- //= require ./utilFunctions
4
- //= require ./objToParam
5
- //= require ./ModuleSupport
6
- //= require ./loader
7
- //= require ./MakeBaseClass
8
- //= require ./el
9
- //= require ./dom-polyfills
10
- //= require ./dom
11
- //= require ./format
12
- //= require ./promise_helpers
13
- //= require ./results
1
+ //=require ./all
14
2
  <% if Lanes.env.development?
15
3
  require_asset "./development"
16
4
  else
@@ -2,7 +2,7 @@
2
2
  #
3
3
  GECKO = /Gecko\//.test(navigator.userAgent)
4
4
 
5
- class Loader
5
+ class Lanes.lib.AssetLoader
6
6
 
7
7
  constructor: (urls, cb) ->
8
8
  finished = 0
@@ -91,7 +91,7 @@ Lanes.lib.RequestAssets = (urls...) ->
91
91
  for url, i in urls
92
92
  urls[i] = Lanes.config.assets_path_prefix.concat( '/', urls[i] )
93
93
  new _.Promise( (resolve, reject) ->
94
- new Loader(urls, (completed) ->
94
+ new Lanes.lib.AssetLoader(urls, (completed) ->
95
95
  failures = _.pick(completed, (status, url) -> !status.success )
96
96
  if _.isEmpty(failures)
97
97
  resolve(completed)
@@ -122,6 +122,8 @@ lcDash = (char, match, index) ->
122
122
 
123
123
  originalTitleize = _.titleize
124
124
 
125
+
126
+ # some are taken from https://github.com/epeli/underscore.string
125
127
  _.mixin({
126
128
  dasherize: (str) ->
127
129
  _.trim(str).replace(/([A-Z])/g, lcDash).replace(/[-_\s]+/g, '-').toLowerCase()
@@ -144,6 +146,16 @@ _.mixin({
144
146
  isPromise: (obj) ->
145
147
  !!obj && (_.isObject(obj) || _.isFunction(obj)) && _.isFunction(obj.then)
146
148
 
149
+ classify: (str) ->
150
+ s = _.toString(str)
151
+ @capitalize(@camelCase(s.replace(/[\W_]/g, ' ')).replace(/\s/g, ''))
152
+
153
+ humanize: (str) ->
154
+ @capitalize(@trim(@underscored(str).replace(/_id$/, '').replace(/_/g, ' ')))
155
+
156
+ underscored: (str) ->
157
+ return @trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/[-\s]+/g, '_').toLowerCase()
158
+
147
159
  isBlank: (value) ->
148
160
  switch true
149
161
  when _.isDate(value)
@@ -4,7 +4,7 @@
4
4
  # Note! An AssociationMap is created for each type of Model, and #
5
5
  # is shared between all instances #
6
6
  # ------------------------------------------------------------------ #
7
- class Lanes.Models.AssocationMap
7
+ class Lanes.Models.AssociationMap
8
8
  constructor: (@klass) ->
9
9
  @klass::derived ||= {}
10
10
  @definitions = @klass::associations
@@ -45,6 +45,8 @@ class Lanes.Models.AssocationMap
45
45
  getOptions: (name, model) ->
46
46
  definition = @definitions[name]
47
47
  options = { parent: model }
48
+ if definition.inverse
49
+ options[ definition.inverse.name ] = model
48
50
  if definition.options
49
51
  _.extend(options, Lanes.u.resultsFor(model, definition.options))
50
52
  options
@@ -131,27 +133,34 @@ class Lanes.Models.AssocationMap
131
133
  continue if not @exists(name) or
132
134
  (_.isEmpty(value) and not @isCreated(model, name))
133
135
 
134
- association = model[name]
136
+ definition = @definitions[name]
135
137
 
136
- if association.isProxy and Lanes.u.isModel(value) and not value.isProxy
137
- association.replaceWithModel(value, association_name: name)
138
+ if @isCreated(model, name)
139
+ association = model[name]
140
+ # nothing to do if setting to same object
141
+ continue if value is association
138
142
 
139
- else if Lanes.u.isModel(association)
140
- continue if association is value
141
-
142
- if !value
143
- association.clear()
144
- else
145
- model.set(this.pk(name), value.id, options) if value.id
146
- if Lanes.u.isModel(value)
147
- @replace(model, name, value)
143
+ if association.isProxy and Lanes.u.isModel(value) and not value.isProxy
144
+ association.replaceWithModel(value, association_name: name)
145
+ else if definition.model
146
+ if value
147
+ @_setModel(model, name, value, options, fn_name)
148
148
  else
149
- association[fn_name]( value )
150
-
151
- if options?.silent isnt true and not association.isProxy
152
- model.trigger("change:#{name}", value, {})
149
+ association.clear()
150
+ else
151
+ if value then association[fn_name]( value, options ) else association.clear()
153
152
  else
154
- if value then association[fn_name]( value, options ) else association.clear()
153
+ @_setModel(model, name, value, options, fn_name)
154
+
155
+ _setModel: (model, name, value, options, fn_name) ->
156
+ model.set(this.pk(name), value.id, options) if value.id
157
+
158
+ if Lanes.u.isModel(value)
159
+ @replace(model, name, value)
160
+ else
161
+ model[name][fn_name]( value )
162
+ if options?.silent isnt true
163
+ model.trigger("change:#{name}", value, {})
155
164
 
156
165
  pk: (name) ->
157
166
  def = @definitions[name]
@@ -20,6 +20,9 @@ class BaseModel
20
20
  deps: ['invalidAttributes'], fn: ->
21
21
  _.isEmpty @invalidAttributes #_calculateInvalidAttributes()
22
22
 
23
+ hasErrors:
24
+ deps: ['errors'], fn: -> not _.isEmpty(@errors)
25
+
23
26
  errorMessage:
24
27
  deps:['errors'], fn: ->
25
28
  if !@errors then ''
@@ -198,9 +201,14 @@ class BaseModel
198
201
  data = this.getAttributes(props:true, true)
199
202
  else
200
203
  data = this.unsavedAttributes()
201
- if this.associations && (!_.isEmpty(data) || !this.isNew()) && !options.excludeAssociations
202
- data.id = this.getId() unless this.isNew()
203
- _.extend(data, this.associations.dataForSave(this, options))
204
+ if this.associations && !options.excludeAssociations
205
+ # empty associations are not included
206
+ associationData = this.associations.dataForSave(this, options)
207
+ _.extend(data, associationData)
208
+ # if submitting associations, we must include our id so they can be updated
209
+ unless this.isNew() or _.isEmpty(associationData)
210
+ data[@idAttribute] = this.getId()
211
+
204
212
  data
205
213
 
206
214
  unCacheDerived: (name) ->
@@ -255,7 +263,7 @@ class BaseModel
255
263
  klass::session['updated_at'] ||= 'date'
256
264
 
257
265
  if klass::associations
258
- klass::associations = new Lanes.Models.AssocationMap(klass)
266
+ klass::associations = new Lanes.Models.AssociationMap(klass)
259
267
 
260
268
  @afterExtended: (klass) ->
261
269
  return if klass::abstractClass
@@ -123,7 +123,9 @@ class ModelsCollection
123
123
 
124
124
  # true if any models have unsaved data
125
125
  isDirty: ->
126
- !!this.findWhere(isDirty: true)
126
+ !!@find( (model) ->
127
+ _.result(model, 'isDirty') isnt false
128
+ )
127
129
 
128
130
  url: -> @model::urlRoot()
129
131
 
@@ -140,8 +142,8 @@ class ModelsCollection
140
142
  dataForSave: (options) ->
141
143
  unsaved = []
142
144
  for model in @models
143
- if options.saveAll || model.isDirty
144
- unsaved.push( model.dataForSave(options) )
145
+ if options.saveAll || model.isDirty isnt false
146
+ unsaved.push( model.dataForSave?(options) or model.serialize())
145
147
  unsaved
146
148
 
147
149
  mixins:[