lanes 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/client/lanes/Config.coffee +3 -1
- data/client/lanes/access/screens/user-management/UserManagement.cjsx +2 -2
- data/client/lanes/components/grid/Body.cjsx +8 -1
- data/client/lanes/components/grid/EditingMixin.cjsx +5 -2
- data/client/lanes/components/grid/Grid.cjsx +1 -1
- data/client/lanes/components/grid/PopOverMixin.cjsx +1 -1
- data/client/lanes/components/grid/row-editor.scss +6 -0
- data/client/lanes/components/grid/styles.scss +1 -1
- data/client/lanes/components/record-finder/RecordFinder.cjsx +22 -16
- data/client/lanes/components/select-field/SelectField.cjsx +20 -27
- data/client/lanes/components/shared/DateTime.cjsx +12 -23
- data/client/lanes/components/shared/DisplayValue.cjsx +5 -10
- data/client/lanes/components/shared/FieldMixin.cjsx +107 -56
- data/client/lanes/components/shared/FieldWrapper.cjsx +59 -6
- data/client/lanes/components/shared/FormGroup.cjsx +3 -8
- data/client/lanes/components/shared/Helpers.coffee +1 -1
- data/client/lanes/components/shared/Icon.cjsx +1 -1
- data/client/lanes/components/shared/ImageAsset.cjsx +46 -0
- data/client/lanes/components/shared/Input.cjsx +5 -1
- data/client/lanes/components/shared/InputFieldMixin.cjsx +8 -27
- data/client/lanes/components/shared/NumberInput.cjsx +11 -4
- data/client/lanes/components/shared/RadioField.cjsx +18 -8
- data/client/lanes/components/shared/ScreenWrapper.cjsx +1 -1
- data/client/lanes/components/shared/TextArea.cjsx +15 -0
- data/client/lanes/components/shared/ToggleField.cjsx +11 -19
- data/client/lanes/components/shared/fields.scss +22 -76
- data/client/lanes/components/shared/{image-saver.scss → image-asset.scss} +1 -1
- data/client/lanes/components/shared/styles.scss +1 -1
- data/client/lanes/components/toolbar/RemoteChangeSets.cjsx +13 -10
- data/client/lanes/components/toolbar/changes-notification.scss +14 -1
- data/client/lanes/extension/Base.coffee +5 -1
- data/client/lanes/models/Asset.coffee +81 -0
- data/client/lanes/models/AssociationMap.coffee +14 -5
- data/client/lanes/models/AssociationProxy.coffee +9 -6
- data/client/lanes/models/Base.coffee +6 -3
- data/client/lanes/models/Collection.coffee +1 -3
- data/client/lanes/models/JobStatus.coffee +3 -0
- data/client/lanes/models/Query.coffee +3 -2
- data/client/lanes/models/Sync.coffee +6 -4
- data/client/lanes/react/mixins/Access.coffee +1 -1
- data/client/lanes/react/mixins/Data.coffee +33 -31
- data/client/lanes/screens/Commands.coffee +0 -1
- data/client/lanes/screens/Definitions.coffee +1 -1
- data/client/lanes/screens/SystemSettings.cjsx +23 -11
- data/client/lanes/screens/UserPreferences.cjsx +3 -2
- data/client/lanes/styles/bootstrap-custom-grid.scss +6 -4
- data/client/lanes/styles/fonts.scss +9 -0
- data/client/lanes/styles/global/styles.scss +1 -0
- data/client/lanes/styles/variables.scss +1 -1
- data/client/lanes/testing/Helpers.coffee +5 -3
- data/client/lanes/vendor/development/base.js +17206 -19211
- data/client/lanes/vendor/development/calendar.js +67 -471
- data/client/lanes/vendor/development/commons.js +21680 -19814
- data/client/lanes/vendor/development/helpers.js +40 -29
- data/client/lanes/vendor/development/toggle.js +22 -19
- data/client/lanes/vendor/development/widgets.js +2476 -2625
- data/client/lanes/vendor/production/base.js +19034 -21038
- data/client/lanes/vendor/production/calendar.js +67 -471
- data/client/lanes/vendor/production/commons.js +21369 -19136
- data/client/lanes/vendor/production/toggle.js +22 -19
- data/client/lanes/vendor/production/widgets.js +2476 -2625
- data/client/lanes/vendor/styles/widgets.scss +2 -0
- data/client/lanes/workspace/FirstRun.cjsx +69 -0
- data/client/lanes/workspace/Navbar.cjsx +4 -2
- data/client/lanes/workspace/ScreenView.cjsx +9 -1
- data/client/lanes/workspace/index.js +1 -0
- data/client/lanes/workspace/styles/screens.scss +11 -0
- data/config/database.yml +2 -2
- data/db/migrate/01_create_system_settings.rb +0 -1
- data/db/migrate/02_create_assets.rb +15 -0
- data/lanes.gemspec +25 -28
- data/lib/lanes/access/authentication_provider.rb +20 -7
- data/lib/lanes/access/role_collection.rb +9 -2
- data/lib/lanes/api/default_routes.rb +4 -4
- data/lib/lanes/api/formatted_reply.rb +15 -11
- data/lib/lanes/api/handlers/asset.rb +37 -0
- data/lib/lanes/api/request_wrapper.rb +18 -4
- data/lib/lanes/api/routing.rb +17 -6
- data/lib/lanes/api/updates.rb +1 -1
- data/lib/lanes/api.rb +1 -1
- data/lib/lanes/asset.rb +38 -0
- data/lib/lanes/concerns/all.rb +1 -1
- data/lib/lanes/concerns/asset_uploader.rb +60 -0
- data/lib/lanes/configuration.rb +1 -2
- data/lib/lanes/extension.rb +1 -1
- data/lib/lanes/logger.rb +26 -16
- data/lib/lanes/system_settings.rb +13 -8
- data/lib/lanes/version.rb +1 -1
- data/lib/lanes.rb +1 -0
- data/npm-build/base.js +1 -1
- data/npm-build/package.json +9 -9
- data/spec/command-reference-files/initial/Gemfile +1 -1
- data/spec/fixtures/logo.png +0 -0
- data/spec/lanes/components/grid/PopoverEditorSpec.coffee +48 -0
- data/spec/server/asset_spec.rb +34 -0
- data/spec/server/spec_helper.rb +14 -2
- metadata +118 -127
- data/client/lanes/components/shared/ControlLabel.cjsx +0 -45
- data/client/lanes/components/shared/ImageSaver.cjsx +0 -33
- data/client/lanes/models/SystemSettings.coffee +0 -0
- data/client/lanes/models/mixins/FileSupport.coffee +0 -60
- data/lib/lanes/api/handlers/file.rb +0 -26
- data/lib/lanes/concerns/image_uploader.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98fcf2619dab60aa9576d6e71d67be4385a56ef6
|
4
|
+
data.tar.gz: a8d42b6cec864bc69e333bff826689378551c033
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e3d155815b2991ab8221bf0faca2e44005e44bcebe4900d9273ed7913d830a971b647faf9ba1002365de6113610a13a9286e876679c572fe5037fbe9f2ccd96
|
7
|
+
data.tar.gz: 7e51bfeecd2a829219816d435f7a636829d0e90f388ec85ff961cdbc9e5e2cbc8ecd4485ec3c0c04a240fdd5d84b7557e61130ed471b3487c6b002d6b25cb237
|
data/.gitignore
CHANGED
data/client/lanes/Config.coffee
CHANGED
@@ -4,9 +4,11 @@ class SystemSettings extends Lanes.Models.Base
|
|
4
4
|
|
5
5
|
props:
|
6
6
|
id: {type:"integer"}
|
7
|
-
logo: "file"
|
8
7
|
settings: "object"
|
9
8
|
|
9
|
+
associations:
|
10
|
+
logo: { model: "Lanes.Models.Asset" }
|
11
|
+
|
10
12
|
modelTypeIdentifier: -> 'system-settings'
|
11
13
|
url: -> Lanes.config.api_path + '/system-settings'
|
12
14
|
initialize: ->
|
@@ -19,7 +19,7 @@ class Lanes.Access.Screens.UserManagement extends Lanes.React.Screen
|
|
19
19
|
id="role_names"
|
20
20
|
key="row-select"
|
21
21
|
queryModel={Lanes.Models.User}
|
22
|
-
editOnly multiSelect writable
|
22
|
+
fieldOnly editOnly multiSelect writable
|
23
23
|
model={model}
|
24
24
|
labelField='name'
|
25
25
|
getSelection={@rolesForUser}
|
@@ -33,7 +33,7 @@ class Lanes.Access.Screens.UserManagement extends Lanes.React.Screen
|
|
33
33
|
<LC.ScreenWrapper identifier="user-management" flexVertical>
|
34
34
|
<h1>Users Management</h1>
|
35
35
|
<LC.Grid
|
36
|
-
editorProps={height:
|
36
|
+
editorProps={height: 350, syncImmediatly: true}
|
37
37
|
query={@state.query}
|
38
38
|
allowCreate
|
39
39
|
editor={Lanes.Access.Screens.UserManagement.Editor}
|
@@ -15,7 +15,6 @@ class Lanes.Components.Grid.Body extends Lanes.React.BaseComponent
|
|
15
15
|
)
|
16
16
|
left = ev.clientX - ourBounds.left
|
17
17
|
selectedIndex = (if @props.selectedIndex == index then null else index)
|
18
|
-
|
19
18
|
selectedModel = if selectedIndex?
|
20
19
|
@props.query.results.modelAt(selectedIndex, clone: true)
|
21
20
|
else null
|
@@ -23,6 +22,14 @@ class Lanes.Components.Grid.Body extends Lanes.React.BaseComponent
|
|
23
22
|
{top, left, container: ourBounds, rowHeight: clickBounds.height}
|
24
23
|
)
|
25
24
|
|
25
|
+
getDefaultEditingPosition: ->
|
26
|
+
el = _.dom(@).el
|
27
|
+
container = el.getBoundingClientRect()
|
28
|
+
{
|
29
|
+
container, top: 0, left: container.width * 0.4
|
30
|
+
rowHeight: el.querySelector('.r')?.clientHeight || 50
|
31
|
+
}
|
32
|
+
|
26
33
|
convertValue: (value, field) ->
|
27
34
|
if @fieldConvertors[field.type] then @fieldConvertors[field.type](value) else value
|
28
35
|
|
@@ -16,6 +16,7 @@ Lanes.Components.Grid.EditingMixin = {
|
|
16
16
|
|
17
17
|
editorTypes:
|
18
18
|
text: (props) ->
|
19
|
+
props.value ||= ''
|
19
20
|
<input type="text" {...props}
|
20
21
|
onChange={_.partial(@onFieldChange, _, props.field)} />
|
21
22
|
bigdec: (props) ->
|
@@ -33,7 +34,8 @@ Lanes.Components.Grid.EditingMixin = {
|
|
33
34
|
listenNetworkEvents: true
|
34
35
|
getDefaultProps: -> editors: {}
|
35
36
|
componentDidMount: ->
|
36
|
-
|
37
|
+
qs = if @props.initiallyFocusedField then ".field[data-id=\"#{@props.initiallyFocusedField}\"] input" else 'input'
|
38
|
+
_.defer => _.dom(@).qs(qs).focusAndSelect()
|
37
39
|
|
38
40
|
renderControls: ->
|
39
41
|
if @props.allowDelete
|
@@ -55,7 +57,7 @@ Lanes.Components.Grid.EditingMixin = {
|
|
55
57
|
</div>
|
56
58
|
|
57
59
|
getFieldValue: (field) ->
|
58
|
-
@props.model[field.id]
|
60
|
+
@props.model[field.id] || ''
|
59
61
|
|
60
62
|
onDateFieldChange: (date, field) ->
|
61
63
|
@props.model[field.id] = date
|
@@ -94,6 +96,7 @@ Lanes.Components.Grid.EditingMixin = {
|
|
94
96
|
@renderDisplayValue(props)
|
95
97
|
|
96
98
|
<div key={field.id}
|
99
|
+
data-id={field.id}
|
97
100
|
style = {@props.cellStyles.styles[index]}
|
98
101
|
className = {_.classnames('field', @props.cellStyles.classes[index])}
|
99
102
|
>
|
@@ -62,7 +62,7 @@ class Lanes.Components.Grid extends Lanes.React.Component
|
|
62
62
|
editing = _.extend({}, options, {
|
63
63
|
index: index,
|
64
64
|
model: options.model or @props.query.results.modelAt(index)
|
65
|
-
position: options.position
|
65
|
+
position: options.position || @refs.body.getDefaultEditingPosition()
|
66
66
|
})
|
67
67
|
set = (attrs = {}) =>
|
68
68
|
if @props.editor and false isnt @props.commands?.isEditing()
|
@@ -22,7 +22,7 @@ Lanes.Components.Grid.PopoverMixin = {
|
|
22
22
|
else
|
23
23
|
props.placement = 'right'
|
24
24
|
props.positionLeft = position.left
|
25
|
-
props.arrowOffsetTop = Math.min(position.top, (@props.height - 75))
|
25
|
+
props.arrowOffsetTop = Math.min(position.top + 20, (@props.height - 75))
|
26
26
|
props.positionTop = Math.max(5, position.top - props.arrowOffsetTop + (position.rowHeight / 2))
|
27
27
|
|
28
28
|
<div className="editor po">
|
@@ -29,6 +29,12 @@
|
|
29
29
|
&.center input { text-align: center; }
|
30
30
|
&.right input { text-align: right; }
|
31
31
|
}
|
32
|
+
.lanes-field, .form-group {
|
33
|
+
flex: 1;
|
34
|
+
display: flex;
|
35
|
+
align-items: center;
|
36
|
+
margin: 0;
|
37
|
+
}
|
32
38
|
}
|
33
39
|
.controls .buttons {
|
34
40
|
background: $editor-background;
|
@@ -7,6 +7,10 @@ class Lanes.Components.RecordFinder extends Lanes.React.Component
|
|
7
7
|
commands: React.PropTypes.object
|
8
8
|
onModelSet: React.PropTypes.func
|
9
9
|
|
10
|
+
mixins: [
|
11
|
+
Lanes.Components.Form.InputFieldMixin
|
12
|
+
]
|
13
|
+
|
10
14
|
contextTypes:
|
11
15
|
viewport: Lanes.PropTypes.State.isRequired
|
12
16
|
|
@@ -43,27 +47,29 @@ class Lanes.Components.RecordFinder extends Lanes.React.Component
|
|
43
47
|
this.loadCurrentSelection()
|
44
48
|
null
|
45
49
|
|
46
|
-
getValue: ->
|
50
|
+
getValue: (ev) ->
|
47
51
|
value = if @props.parentModel
|
48
52
|
@props.parentModel[@props.associationName][@props.name]
|
49
53
|
else
|
50
54
|
@props.model[@props.name]
|
51
55
|
value or ''
|
52
56
|
|
53
|
-
|
54
|
-
|
57
|
+
|
58
|
+
renderInputField: (props, handlers) ->
|
59
|
+
# editOnly writable
|
55
60
|
model = @props.parentModel or @props.model
|
56
|
-
label =
|
57
|
-
<LC.ControlLabel titleOnly {...@props} model={model} />
|
58
61
|
|
59
|
-
<
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
62
|
+
<BS.InputGroup>
|
63
|
+
|
64
|
+
<BS.FormControl
|
65
|
+
{...props} {...handlers}
|
66
|
+
onChange={@fieldMixinSetValue}
|
67
|
+
value={@getValue()}
|
68
|
+
onKeyPress={@onKeyPress}
|
69
|
+
/>
|
70
|
+
|
71
|
+
<BS.InputGroup.Button>
|
72
|
+
<button className="btn btn-primary icon icon-search icon-lg"
|
73
|
+
onClick={@showFinder} />
|
74
|
+
</BS.InputGroup.Button>
|
75
|
+
</BS.InputGroup>
|
@@ -18,6 +18,8 @@ class Lanes.Components.SelectField extends Lanes.React.Component
|
|
18
18
|
getDefaultProps: ->
|
19
19
|
labelField: 'label', idField: 'id'
|
20
20
|
|
21
|
+
fieldClassName: 'select'
|
22
|
+
|
21
23
|
dataObjects:
|
22
24
|
query: ->
|
23
25
|
src = @props.queryModel or
|
@@ -31,16 +33,6 @@ class Lanes.Components.SelectField extends Lanes.React.Component
|
|
31
33
|
]
|
32
34
|
})
|
33
35
|
|
34
|
-
renderDisplayValue: ->
|
35
|
-
value = @getValue()
|
36
|
-
label = if _.isArray(value)
|
37
|
-
_.toSentence( _.map(value, @props.labelField) )
|
38
|
-
else if _.isObject(value)
|
39
|
-
value[@props.labelField]
|
40
|
-
else
|
41
|
-
value
|
42
|
-
<span>{label}</span>
|
43
|
-
|
44
36
|
getValue: ->
|
45
37
|
return undefined if @state.isOpen and not @props.multiSelect
|
46
38
|
return @state.tempDisplayValue if @state.tempDisplayValue
|
@@ -102,10 +94,23 @@ class Lanes.Components.SelectField extends Lanes.React.Component
|
|
102
94
|
isBusy: ->
|
103
95
|
!!(@state.requestInProgress or @query.results.requestInProgress)
|
104
96
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
97
|
+
renderDisplay: (props) ->
|
98
|
+
value = @getValue()
|
99
|
+
label = if _.isArray(value)
|
100
|
+
_.toSentence( _.map(value, @props.labelField) )
|
101
|
+
else if _.isObject(value)
|
102
|
+
value[@props.labelField]
|
103
|
+
else
|
104
|
+
value
|
105
|
+
<BS.FormControl.Static {...props}>
|
106
|
+
{label}
|
107
|
+
</BS.FormControl.Static>
|
108
|
+
|
109
|
+
|
110
|
+
renderEdit: (props) ->
|
111
|
+
type = if @props.multiSelect then 'Multiselect' else 'Combobox'
|
112
|
+
Component = Lanes.Vendor.ReactWidgets[type]
|
113
|
+
<Component
|
109
114
|
ref="select"
|
110
115
|
className={@props.className}
|
111
116
|
open={@state.isOpen}
|
@@ -121,16 +126,4 @@ class Lanes.Components.SelectField extends Lanes.React.Component
|
|
121
126
|
onBlur={@onFieldInteraction}
|
122
127
|
value={@getValue()}
|
123
128
|
{...props}
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
if @props.unstyled
|
128
|
-
select
|
129
|
-
else
|
130
|
-
<LC.FormGroup
|
131
|
-
{...@props}
|
132
|
-
className={@formGroupClassNames()}
|
133
|
-
label={@getLabelValue()}
|
134
|
-
>
|
135
|
-
{select}
|
136
|
-
</LC.FormGroup>
|
129
|
+
/>
|
@@ -3,7 +3,7 @@ class Lanes.Components.DateTime extends Lanes.React.Component
|
|
3
3
|
mixins: [
|
4
4
|
Lanes.Components.Form.FieldMixin
|
5
5
|
]
|
6
|
-
|
6
|
+
fieldClassName: 'date-time'
|
7
7
|
|
8
8
|
getDefaultProps: ->
|
9
9
|
format: 'ddd, MMM Do YYYY, h:mm a'
|
@@ -12,37 +12,26 @@ class Lanes.Components.DateTime extends Lanes.React.Component
|
|
12
12
|
unlabled: React.PropTypes.bool
|
13
13
|
format: React.PropTypes.string
|
14
14
|
|
15
|
-
renderDisplayValue: ->
|
16
|
-
<span>{_.moment(@model[@props.name]).format(@props.format)}</span>
|
17
|
-
|
18
|
-
getValue: ->
|
19
|
-
@refs.input.getValue()
|
20
15
|
|
21
16
|
handleKeyDown: (ev) ->
|
22
17
|
@props.onEnter() if ev.key is 'Enter'
|
23
18
|
|
24
19
|
handleDateTimeChange: (val) ->
|
25
|
-
@
|
20
|
+
@fieldMixinSetValue({target: {value: val}})
|
21
|
+
|
26
22
|
|
27
|
-
|
28
|
-
|
23
|
+
renderDisplay: (props) ->
|
24
|
+
<BS.FormControl.Static {...props}>
|
25
|
+
{_.moment(@model[@props.name]).format(@props.format)}
|
26
|
+
</BS.FormControl.Static>
|
27
|
+
|
28
|
+
renderEdit: (props) ->
|
29
29
|
props = _.extend({
|
30
30
|
ref: 'control'
|
31
|
-
|
32
|
-
label: if @props.unlabeled then false else label
|
33
|
-
value: value
|
31
|
+
value: @fieldMixinGetValue()
|
34
32
|
onChange: @handleDateTimeChange
|
35
33
|
}, @props)
|
36
|
-
if @props.inputOnly then @renderPlain(props) else @renderStyled(props, label)
|
37
|
-
|
38
|
-
renderPlain: (props) ->
|
39
|
-
<Lanes.Vendor.ReactWidgets.DateTimePicker {...props} />
|
40
34
|
|
41
|
-
|
42
|
-
<LC.FormGroup
|
35
|
+
<Lanes.Vendor.ReactWidgets.DateTimePicker
|
43
36
|
{...props}
|
44
|
-
|
45
|
-
label={label}
|
46
|
-
>
|
47
|
-
<Lanes.Vendor.ReactWidgets.DateTimePicker {...props} />
|
48
|
-
</LC.FormGroup>
|
37
|
+
/>
|
@@ -3,14 +3,9 @@ class Lanes.Components.DisplayValue extends Lanes.React.Component
|
|
3
3
|
mixins: [
|
4
4
|
Lanes.Components.Form.FieldMixin
|
5
5
|
]
|
6
|
-
|
6
|
+
fieldClassName: 'display-value'
|
7
7
|
|
8
|
-
renderEdit: (
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
className={@formGroupClassNames()}
|
13
|
-
label={@getLabelValue()}
|
14
|
-
>
|
15
|
-
{value}
|
16
|
-
</LC.FormGroup>
|
8
|
+
renderEdit: (props) ->
|
9
|
+
<BS.FormControl.Static {...props}>
|
10
|
+
{@fieldMixinGetValue()}
|
11
|
+
</BS.FormControl.Static>
|
@@ -1,38 +1,40 @@
|
|
1
1
|
Lanes.Components.Form || = {}
|
2
2
|
|
3
|
+
|
3
4
|
Lanes.Components.Form.FieldMixin = {
|
5
|
+
|
4
6
|
bindDataEvents: ->
|
5
7
|
model: "change:#{@props.name} remote-update:#{@props.name} invalid-fields invalid-field:#{@getInvalidFieldName()}"
|
6
8
|
|
7
9
|
mixins: [
|
8
10
|
Lanes.React.Mixins.Access
|
9
|
-
Lanes.React.Mixins.FieldErrors
|
10
11
|
Lanes.React.Mixins.ReadEditingState
|
12
|
+
|
13
|
+
Lanes.React.Mixins.FieldErrors
|
11
14
|
]
|
12
15
|
|
13
16
|
pubsub: false
|
14
17
|
propTypes:
|
15
|
-
model:
|
16
|
-
name:
|
18
|
+
model: Lanes.PropTypes.State.isRequired
|
19
|
+
name: React.PropTypes.string.isRequired
|
20
|
+
unlabeled: React.PropTypes.bool
|
21
|
+
fieldOnly: React.PropTypes.bool
|
22
|
+
onChange: React.PropTypes.func
|
23
|
+
unstyled: React.PropTypes.bool
|
24
|
+
getValue: React.PropTypes.func
|
25
|
+
setValue: React.PropTypes.func
|
17
26
|
align: React.PropTypes.oneOf([
|
18
27
|
'right', 'left', 'center'
|
19
28
|
])
|
20
29
|
label: React.PropTypes.oneOfType([
|
21
30
|
React.PropTypes.string, React.PropTypes.element
|
22
31
|
])
|
23
|
-
onChange: React.PropTypes.func
|
24
|
-
unstyled: React.PropTypes.bool
|
25
|
-
getValue: React.PropTypes.func
|
26
|
-
setValue: React.PropTypes.func
|
27
|
-
|
28
|
-
_getValue: ->
|
29
|
-
if @props.getValue
|
30
|
-
@props.getValue.call(@model, @props) or ''
|
31
|
-
else
|
32
|
-
@model[@props.name] or ''
|
33
32
|
|
34
|
-
|
35
|
-
if @props.
|
33
|
+
fieldMixinSetValue: (ev) ->
|
34
|
+
if @props.onChange
|
35
|
+
@props.onChange(ev)
|
36
|
+
unless true is ev.isDefaultPrevented?()
|
37
|
+
@model[@props.name] = ev.target.value
|
36
38
|
|
37
39
|
componentWillUnmount: ->
|
38
40
|
clearTimeout(@state.pendingChangeSetDelay) if @state.pendingChangeSetDelay
|
@@ -42,61 +44,110 @@ Lanes.Components.Form.FieldMixin = {
|
|
42
44
|
|
43
45
|
setDataState: (state, evname) ->
|
44
46
|
displayChangeset = @model.updatingFromChangeset and @model.changedAttributes()[@props.name]
|
45
|
-
if displayChangeset
|
47
|
+
if displayChangeset and not @state.pendingChangeSetDelay
|
46
48
|
pendingChangeSetDelay = _.delay(@_unsetChangeSet, 2000)
|
47
49
|
@setState(_.extend( state, {pendingChangeSetDelay, displayChangeset}))
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
@props.
|
51
|
+
_fieldMixinGetLabelValue: ->
|
52
|
+
@getLabelValue?() ||
|
53
|
+
@props.label ||
|
54
|
+
_.titleize _.humanize @props.name
|
55
|
+
|
56
|
+
fieldMixinGetValue: ->
|
57
|
+
value = if @props.getValue
|
58
|
+
@props.getValue.call(@model, @props)
|
59
|
+
else if @props.value?
|
60
|
+
@props.value
|
61
|
+
else if @getValue
|
62
|
+
@getValue()
|
52
63
|
else
|
53
|
-
@
|
54
|
-
|
55
|
-
|
56
|
-
renderMixinDisplayValue: ->
|
57
|
-
value = @_getValue() || ""
|
58
|
-
value = String(value) if _.isObject(value) or not value
|
59
|
-
<span>{value}</span>
|
60
|
-
|
61
|
-
formGroupClassNames: ->
|
62
|
-
_.classnames( _.result(this, 'formGroupClass'), {
|
63
|
-
changeset: @state.displayChangeset
|
64
|
-
})
|
65
|
-
|
66
|
-
_mixinRenderValue: (label, className) ->
|
67
|
-
value = (@renderDisplayValue || @renderMixinDisplayValue)?()
|
68
|
-
className = _.classnames(@props.className, className, @formGroupClassNames(), {
|
69
|
-
"align-#{@props.align}": @props.align
|
70
|
-
})
|
71
|
-
if @props.unstyled
|
72
|
-
value
|
64
|
+
@model[@props.name]
|
65
|
+
if _.isBlank(value)
|
66
|
+
''
|
73
67
|
else
|
74
|
-
|
75
|
-
className={className} label={label}
|
76
|
-
>
|
77
|
-
{value}
|
78
|
-
</LC.FormGroup>
|
79
|
-
|
80
|
-
getLabelValue: ->
|
81
|
-
@props.label || _.titleize _.humanize @props.name
|
68
|
+
value
|
82
69
|
|
83
|
-
|
70
|
+
_fieldMixinRenderFormGroup: (child, props, options) ->
|
71
|
+
if (invalidMsg = @fieldInvalidValueMessage())
|
72
|
+
msg = <BS.HelpBlock>{invalidMsg}</BS.HelpBlock>
|
84
73
|
unless @props.unlabeled
|
85
74
|
label =
|
86
|
-
<
|
87
|
-
|
88
|
-
|
75
|
+
<BS.ControlLabel>
|
76
|
+
{@_fieldMixinGetLabelValue()}
|
77
|
+
</BS.ControlLabel>
|
78
|
+
|
79
|
+
<BS.Col {...props}>
|
80
|
+
<BS.FormGroup validationState={options.validationState}>
|
81
|
+
{label}
|
82
|
+
{child}
|
83
|
+
<BS.FormControl.Feedback />
|
84
|
+
{msg}
|
85
|
+
</BS.FormGroup>
|
86
|
+
</BS.Col>
|
87
|
+
|
88
|
+
_fieldMixinRenderEdit: (props) ->
|
89
|
+
<BS.FormControl
|
90
|
+
type={@props.type || "text"}
|
91
|
+
value={@fieldMixinGetValue()}
|
92
|
+
{...props}
|
93
|
+
/>
|
94
|
+
|
95
|
+
_fieldMixinRenderDisplay: (props) ->
|
96
|
+
value = @fieldMixinGetValue()
|
97
|
+
value = value.toString() if _.isObject(value)
|
98
|
+
|
99
|
+
<BS.FormControl.Static {...props}>
|
100
|
+
{value}
|
101
|
+
</BS.FormControl.Static>
|
102
|
+
|
103
|
+
_fieldMixinRenderNone: (props) ->
|
104
|
+
<span {...props} />
|
105
|
+
|
106
|
+
renderType: ->
|
89
107
|
if @isEditingRecord()
|
90
108
|
if @hasWriteAccess()
|
91
|
-
|
109
|
+
['edit', 'Edit']
|
92
110
|
else if @hasReadAccess()
|
93
|
-
|
111
|
+
['read-only', 'Display']
|
94
112
|
else
|
95
|
-
|
113
|
+
['none', 'None']
|
96
114
|
else
|
97
115
|
if @hasReadAccess()
|
98
|
-
|
116
|
+
['display', 'Display']
|
99
117
|
else
|
100
|
-
|
118
|
+
['none', 'None']
|
119
|
+
statics:
|
120
|
+
cleanColumnProps: (props) ->
|
121
|
+
_.omit props, 'model', 'label', 'name', 'unlabeled', 'fieldOnly', 'placeholder', 'type'
|
122
|
+
|
123
|
+
renderEmptyColumn: (props = @props) ->
|
124
|
+
props = @cleanColumnProps(props)
|
125
|
+
<BS.Col {...props} />
|
126
|
+
|
127
|
+
render: ->
|
128
|
+
[type, method] = @renderType()
|
129
|
+
options = {}
|
130
|
+
|
131
|
+
hasError = @isFieldValueInvalid()
|
132
|
+
options.validationState = 'warning' if hasError
|
133
|
+
props = LC.Form.FieldMixin.statics.cleanColumnProps(@props)
|
134
|
+
|
135
|
+
props.className = _.classnames(
|
136
|
+
_.result(this, 'fieldClassName'),
|
137
|
+
'lanes-field', type, props.className,
|
138
|
+
( if @props.align then "align-#{@props.align}" else null),
|
139
|
+
{
|
140
|
+
changeset: @state.displayChangeset
|
141
|
+
'has-error': hasError
|
142
|
+
}
|
143
|
+
)
|
144
|
+
|
145
|
+
field = (@[ "render#{method}" ] || @["_fieldMixinRender#{method}"])(
|
146
|
+
if @props.fieldOnly then props else _.omit(props, 'className')
|
147
|
+
)
|
148
|
+
if @props.fieldOnly
|
149
|
+
field
|
150
|
+
else
|
151
|
+
( @['renderFormGroup'] || @['_fieldMixinRenderFormGroup'] )(field, props, options)
|
101
152
|
|
102
153
|
}
|
@@ -1,13 +1,66 @@
|
|
1
1
|
class Lanes.Components.FieldWrapper extends Lanes.React.BaseComponent
|
2
2
|
|
3
3
|
mixins: [
|
4
|
+
Lanes.React.Mixins.Access
|
5
|
+
Lanes.React.Mixins.FieldErrors
|
4
6
|
Lanes.React.Mixins.ReadEditingState
|
5
7
|
]
|
8
|
+
blankElement: 'span'
|
9
|
+
propTypes:
|
10
|
+
model: Lanes.PropTypes.State.isRequired
|
11
|
+
unlabeled: React.PropTypes.bool
|
12
|
+
displayComponent: React.PropTypes.any.isRequired
|
13
|
+
label: React.PropTypes.oneOfType([
|
14
|
+
React.PropTypes.string, React.PropTypes.element
|
15
|
+
])
|
16
|
+
|
17
|
+
renderLabel: ->
|
18
|
+
return null if @props.unlabeled
|
19
|
+
<BS.ControlLabel>
|
20
|
+
{@props.label}
|
21
|
+
</BS.ControlLabel>
|
22
|
+
|
23
|
+
|
24
|
+
renderType: ->
|
25
|
+
if @isEditingRecord()
|
26
|
+
if @hasWriteAccess()
|
27
|
+
['edit', @props.children]
|
28
|
+
else if @hasReadAccess()
|
29
|
+
['display']
|
30
|
+
else
|
31
|
+
['none', @blankElement]
|
32
|
+
else
|
33
|
+
if @hasReadAccess()
|
34
|
+
['display']
|
35
|
+
else
|
36
|
+
['none', @blankElement]
|
6
37
|
|
7
38
|
render: ->
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
39
|
+
[type, child] = @renderType()
|
40
|
+
props = _.omit(@props,
|
41
|
+
'value', 'model', 'value', 'label', 'name'
|
42
|
+
)
|
43
|
+
unless child
|
44
|
+
Comp = @props.displayComponent
|
45
|
+
child = <Comp props />
|
46
|
+
|
47
|
+
if @isFieldValueInvalid()
|
48
|
+
validationState = 'warning'
|
49
|
+
|
50
|
+
if (invalidMsg = @fieldInvalidValueMessage())
|
51
|
+
msg = <BS.HelpBlock>{invalidMsg}</BS.HelpBlock>
|
52
|
+
|
53
|
+
className = _.classnames( 'lanes-field', type, @props.className
|
54
|
+
( if @props.align then "align-#{@props.align}" else null),
|
55
|
+
)
|
56
|
+
|
57
|
+
<BS.Col {...props} className={className}>
|
58
|
+
<BS.FormGroup validationState={validationState}>
|
59
|
+
<BS.ControlLabel>
|
60
|
+
{@renderLabel()}
|
61
|
+
</BS.ControlLabel>
|
62
|
+
{child}
|
63
|
+
<BS.FormControl.Feedback />
|
64
|
+
{msg}
|
65
|
+
</BS.FormGroup>
|
66
|
+
</BS.Col>
|
@@ -20,18 +20,13 @@ class Lanes.Components.FormGroup extends Lanes.React.Component
|
|
20
20
|
display: false == @props.editing
|
21
21
|
'has-error': @isFieldValueInvalid()
|
22
22
|
)
|
23
|
-
|
24
23
|
colProps = _.omit(@props, 'name', 'label', 'type', 'editing', 'display')
|
25
24
|
valueClassNames = _.classnames('value', {
|
26
25
|
"align-#{@props.align}": @props.align
|
27
26
|
})
|
28
27
|
<BS.Col {...colProps} className={className}>
|
29
|
-
<
|
28
|
+
<BS.FormGroup className={valueClassNames}>
|
30
29
|
<LC.ControlLabel {...@props} />
|
31
|
-
|
32
|
-
|
33
|
-
{@props.children}
|
34
|
-
</div>
|
35
|
-
</div>
|
36
|
-
</div>
|
30
|
+
{@props.children}
|
31
|
+
</BS.FormGroup>
|
37
32
|
</BS.Col>
|