lanes 0.5.0 → 0.5.5

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 (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:[