@@ -9,15 +9,18 @@ import { Subscription } from 'rxjs/Subscription';
9
9
export class CustomFormGroup extends FormGroup {
10
10
11
11
// Tells other ClickToEdit components when we're in "edit" mode for the form group.
12
- public _msShowTextbox : Subject < boolean > ;
12
+ public msShowTextbox : Subject < boolean > ;
13
13
14
14
// Tells other ClickToEdit components which control currently has focus
15
- public _msFocusedControl : string ;
15
+ public msFocusedControl : string ;
16
16
17
17
// Overrides the ClickToEdit default behavior to start in edit mode for new items
18
- public _msStartInEditMode : boolean ;
18
+ public msStartInEditMode : boolean ;
19
19
20
- public _msExistenceState : 'original' | 'new' | 'deleted' = 'original' ;
20
+ public msExistenceState : 'original' | 'new' | 'deleted' = 'original' ;
21
+
22
+ // Overrides the ClickToEdit default behavior to remain in edit mode
23
+ public msStayInEditMode : boolean ;
21
24
}
22
25
23
26
export class CustomFormControl extends FormControl {
@@ -33,7 +36,10 @@ export class CustomFormControl extends FormControl {
33
36
export class ClickToEditComponent implements OnInit , AfterViewInit , OnDestroy {
34
37
35
38
public showTextbox = false ;
36
- @Input ( ) group : FormGroup ;
39
+ public group : CustomFormGroup ;
40
+ @Input ( 'group' ) set origGroup ( group : FormGroup ) {
41
+ this . group = group as CustomFormGroup ;
42
+ }
37
43
@Input ( ) name : string ;
38
44
@Input ( ) placeholder : string ;
39
45
@Input ( ) hiddenText : boolean ;
@@ -42,6 +48,8 @@ export class ClickToEditComponent implements OnInit, AfterViewInit, OnDestroy {
42
48
// (i.e. The control's own editable/non-editable state is not affected by the extended fields in the CustomFormGroup its associated with.)
43
49
@Input ( ) alwaysShow : boolean ;
44
50
51
+ @ViewChild ( 'container' ) container : ElementRef ;
52
+
45
53
@ViewChild ( 'target' ) target : ElementRef ;
46
54
47
55
@ContentChild ( TextboxComponent ) textbox : TextboxComponent ;
@@ -52,7 +60,7 @@ export class ClickToEditComponent implements OnInit, AfterViewInit, OnDestroy {
52
60
53
61
private _targetFocusState : 'focused' | 'blurring' | 'blurred' ;
54
62
private _focusFunc = ( e : FocusEvent ) => { this . _targetFocusListener ( e ) ; } ;
55
- private _blurFunc = ( e : FocusEvent ) => { this . _targetBlurListener ( e ) ; } ;
63
+ private _blurFunc = ( e : FocusEvent ) => { this . _targetBlurListener ( e ) ; } ;
56
64
57
65
constructor ( ) { }
58
66
@@ -62,20 +70,21 @@ export class ClickToEditComponent implements OnInit, AfterViewInit, OnDestroy {
62
70
this . control = this . group . controls [ this . name ] as CustomFormControl ;
63
71
64
72
const group = this . group as CustomFormGroup ;
65
- if ( ! group . _msShowTextbox ) {
66
- group . _msShowTextbox = new Subject < boolean > ( ) ;
73
+
74
+ if ( ! group . msShowTextbox ) {
75
+ group . msShowTextbox = new Subject < boolean > ( ) ;
67
76
}
68
77
69
- this . _sub = group . _msShowTextbox . subscribe ( showTextbox => {
70
- this . showTextbox = showTextbox || this . alwaysShow || ( group . _msStartInEditMode && group . pristine ) ;
71
- if ( this . showTextbox && ( this . group as CustomFormGroup ) . _msFocusedControl === this . name ) {
78
+ this . _sub = group . msShowTextbox . subscribe ( showTextbox => {
79
+ this . showTextbox = showTextbox || this . alwaysShow || ( group . msStartInEditMode && group . pristine ) ;
80
+ if ( this . showTextbox && ( this . group as CustomFormGroup ) . msFocusedControl === this . name ) {
72
81
setTimeout ( ( ) => {
73
82
this . _focusChild ( ) ;
74
83
} ) ;
75
84
}
76
85
} ) ;
77
86
78
- if ( group . _msStartInEditMode || this . alwaysShow ) {
87
+ if ( group . msStartInEditMode || this . alwaysShow ) {
79
88
this . showTextbox = true ;
80
89
}
81
90
}
@@ -99,6 +108,14 @@ export class ClickToEditComponent implements OnInit, AfterViewInit, OnDestroy {
99
108
}
100
109
101
110
private _focusChild ( ) {
111
+ if ( ! this . target ) {
112
+ return ;
113
+ }
114
+
115
+ if ( ( this . target . nativeElement as HTMLElement ) . contains ( document . activeElement ) ) {
116
+ return ;
117
+ }
118
+
102
119
if ( this . textbox ) {
103
120
this . textbox . focus ( ) ;
104
121
} else if ( this . dropdown ) {
@@ -111,9 +128,14 @@ export class ClickToEditComponent implements OnInit, AfterViewInit, OnDestroy {
111
128
}
112
129
113
130
onMouseDown ( event : MouseEvent ) {
114
- if ( ! this . showTextbox ) {
131
+ if ( ! this . showTextbox && ! ! this . control && ! this . control . disabled ) {
115
132
event . preventDefault ( ) ;
133
+ event . stopPropagation ( ) ;
116
134
this . _updateShowTextbox ( true ) ;
135
+
136
+ // Simulate 'mousedown', 'mouseup', click' event sequence on the outer-most element.
137
+ // We do this because the actual clicked element will be removed from the DOM before 'mouseup' and 'click' can occur.
138
+ this . _simulateMouseEvents ( this . container . nativeElement , [ 'mousedown' , 'mouseup' , 'click' ] ) ;
117
139
}
118
140
}
119
141
@@ -145,16 +167,24 @@ export class ClickToEditComponent implements OnInit, AfterViewInit, OnDestroy {
145
167
}
146
168
147
169
protected _updateShowTextbox ( show : boolean ) {
170
+ // When an instance with alwaysShow === true gains focus:
171
+ // 1. Do not make the controls in the group become visible if they
172
+ // are not currently visible.
173
+ // 2. If the conrols are already visible because the focus came from
174
+ // another control in the group, make sure they stay visible.
175
+
176
+ const alwaysShowSuffix = '#ALWAYSSHOW' ;
148
177
const group = this . group as CustomFormGroup ;
178
+ const name = this . name + ( this . alwaysShow ? alwaysShowSuffix : '' ) ;
149
179
150
- if ( show ) {
151
- group . _msFocusedControl = this . name ;
152
- } else if ( group . _msFocusedControl === this . name ) {
153
- group . _msFocusedControl = '' ;
180
+ if ( show ) { //gained focus
181
+ group . msFocusedControl = name ;
182
+ } else if ( group . msFocusedControl === name ) { //lost focus
183
+ group . msFocusedControl = '' ;
154
184
}
155
185
156
- if ( ! group . _msFocusedControl || group . _msFocusedControl === this . name ) {
157
- group . _msShowTextbox . next ( show ) ;
186
+ if ( ! group . msFocusedControl || ( group . msFocusedControl === name && ! this . alwaysShow ) ) {
187
+ group . msShowTextbox . next ( show ) ;
158
188
}
159
189
}
160
190
@@ -175,4 +205,27 @@ export class ClickToEditComponent implements OnInit, AfterViewInit, OnDestroy {
175
205
this . _targetFocusState = 'focused' ;
176
206
}
177
207
208
+ private _simulateMouseEvents ( target : HTMLElement , eventTypes : string [ ] ) {
209
+ if ( ! eventTypes || eventTypes . length === 0 ) {
210
+ return ;
211
+ }
212
+
213
+ let newEvent : MouseEvent ;
214
+ if ( typeof ( Event ) === 'function' ) {
215
+ // This isn't IE, so we can use MouseEvent()
216
+ newEvent = new MouseEvent ( eventTypes [ 0 ] , { bubbles : true , cancelable : true } ) ;
217
+ } else {
218
+ // This is IE, so we have to use document.createEvent() and .initEvent()
219
+ newEvent = document . createEvent ( 'MouseEvents' ) ;
220
+ newEvent . initEvent ( eventTypes [ 0 ] , true , true ) ;
221
+ }
222
+
223
+ target . dispatchEvent ( newEvent ) ;
224
+
225
+ setTimeout ( ( ) => {
226
+ eventTypes . splice ( 0 , 1 ) ;
227
+ this . _simulateMouseEvents ( target , eventTypes ) ;
228
+ } ) ;
229
+ }
230
+
178
231
}
0 commit comments