|
/Gruntfile.js
|
0 problems
|
|
/jsduck.external.js
|
0 problems
|
|
/build/tasks/colorize-svg.js
|
0 problems
|
|
/demos/classes/BookletDialog.js
|
0 problems
|
|
/demos/classes/BrokenDialog.js
|
0 problems
|
|
/demos/classes/ButtonStyleShowcaseWidget.js
|
2 problems (2 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 8, Column 2: "Prefer Array#forEach to $.each"
jquery/no-each
|
Error |
Row 10, Column 3: "Prefer Array#forEach to $.each"
jquery/no-each
|
Line |
Source |
1 |
Demo.ButtonStyleShowcaseWidget = function DemoButtonStyleShowcaseWidget( config ) { |
2 |
var widget = this; |
3 |
|
4 |
Demo.ButtonStyleShowcaseWidget.parent.call( this, config ); |
5 |
|
6 |
this.$element.addClass( 'demo-buttonStyleShowcaseWidget' ); |
7 |
|
Error |
Row 8, Column 2: "Prefer Array#forEach to $.each"
jquery/no-each
|
8 |
$.each( this.constructor.static.styles, function ( i, style ) { |
9 |
var $buttonRow = $( '<div>' ); |
Error |
Row 10, Column 3: "Prefer Array#forEach to $.each"
jquery/no-each
|
10 |
$.each( widget.constructor.static.states, function ( j, state ) { |
11 |
$buttonRow.append( |
12 |
new OO.ui.ButtonWidget( $.extend( {}, style, state ) ).$element |
13 |
); |
14 |
} ); |
15 |
widget.$element.append( $buttonRow ); |
16 |
} ); |
17 |
}; |
18 |
|
19 |
OO.inheritClass( Demo.ButtonStyleShowcaseWidget, OO.ui.Widget ); |
20 |
|
21 |
Demo.ButtonStyleShowcaseWidget.static.styles = [ |
22 |
{}, |
23 |
{ |
24 |
flags: [ 'progressive' ] |
25 |
}, |
26 |
{ |
27 |
flags: [ 'destructive' ] |
28 |
}, |
29 |
{ |
30 |
flags: [ 'primary', 'progressive' ] |
31 |
}, |
32 |
{ |
33 |
flags: [ 'primary', 'destructive' ] |
34 |
} |
35 |
]; |
36 |
|
37 |
Demo.ButtonStyleShowcaseWidget.static.states = [ |
38 |
{ |
39 |
label: 'Button' |
40 |
}, |
41 |
{ |
42 |
label: 'Button', |
43 |
icon: 'tag' |
44 |
}, |
45 |
{ |
46 |
label: 'Button', |
47 |
icon: 'tag', |
48 |
indicator: 'down' |
49 |
}, |
50 |
{ |
51 |
icon: 'tag', |
52 |
title: 'Title text' |
53 |
}, |
54 |
{ |
55 |
indicator: 'down' |
56 |
}, |
57 |
{ |
58 |
icon: 'tag', |
59 |
indicator: 'down' |
60 |
}, |
61 |
{ |
62 |
label: 'Button', |
63 |
disabled: true |
64 |
}, |
65 |
{ |
66 |
icon: 'tag', |
67 |
title: 'Title text', |
68 |
disabled: true |
69 |
}, |
70 |
{ |
71 |
indicator: 'down', |
72 |
disabled: true |
73 |
} |
74 |
]; |
75 |
|
|
|
/demos/classes/ContinuousOutlinedBookletDialog.js
|
5 problems (5 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 35, Column 90: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 36, Column 90: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 37, Column 90: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 38, Column 90: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 39, Column 90: "Prefer textContent to $.text"
jquery/no-text
|
Line |
Source |
1 |
Demo.ContinuousOutlinedBookletDialog = function DemoContinuousOutlinedBookletDialog( config ) { |
2 |
Demo.ContinuousOutlinedBookletDialog.parent.call( this, config ); |
3 |
}; |
4 |
OO.inheritClass( Demo.ContinuousOutlinedBookletDialog, OO.ui.ProcessDialog ); |
5 |
Demo.ContinuousOutlinedBookletDialog.static.title = 'Continuous outlined booklet dialog'; |
6 |
Demo.ContinuousOutlinedBookletDialog.static.actions = [ |
7 |
{ action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] }, |
8 |
{ action: 'cancel', label: 'Cancel', flags: [ 'safe', 'back' ] } |
9 |
]; |
10 |
Demo.ContinuousOutlinedBookletDialog.prototype.getBodyHeight = function () { |
11 |
return 250; |
12 |
}; |
13 |
Demo.ContinuousOutlinedBookletDialog.prototype.initialize = function () { |
14 |
var lipsum; |
15 |
Demo.ContinuousOutlinedBookletDialog.parent.prototype.initialize.apply( this, arguments ); |
16 |
this.bookletLayout = new OO.ui.BookletLayout( { |
17 |
outlined: true, |
18 |
continuous: true |
19 |
} ); |
20 |
lipsum = [ |
21 |
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eleifend justo nec erat tempus, quis aliquet augue aliquam. Sed rutrum odio in tellus pharetra, ut mollis est fermentum. ' + |
22 |
'Sed egestas dolor libero, a aliquet sem finibus eu. Morbi dolor nisl, pulvinar vitae maximus sed, lacinia eu ipsum. Fusce rutrum placerat massa, vel vehicula nisi viverra nec. ' + |
23 |
'Nam at turpis vel nisi efficitur tempor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Morbi aliquam pulvinar fermentum. Maecenas rutrum accumsan lorem ac sagittis. ' + |
24 |
'Praesent id nunc gravida, iaculis odio eu, maximus ligula. Praesent ut tellus mollis, pharetra orci vitae, interdum lacus. Nulla sodales lacus eget libero pellentesque tempor.', |
25 |
'Ut a metus elementum, eleifend velit et, malesuada enim.', |
26 |
'Aenean sem eros, rutrum vitae pulvinar at, vulputate id quam. Quisque tincidunt, ligula pulvinar consequat tempor, tellus erat lobortis nisl, non euismod diam nisl ut libero. Etiam mollis, ' + |
27 |
'risus a tincidunt efficitur, ipsum justo ullamcorper sem, id gravida dui lacus quis turpis. In consectetur tincidunt elit in mollis. Sed nec ultricies turpis, at dictum risus. Curabitur ipsum diam, ' + |
28 |
'aliquet sit amet ante eu, congue cursus magna. Donec at lectus in nulla ornare faucibus. Vestibulum mattis massa eu convallis convallis. Sed tristique ut quam non eleifend. Nunc aliquam, nisi non ' + |
29 |
'posuere dictum, est nunc mollis nisl.', |
30 |
'Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce laoreet mi mi, nec tempor erat posuere malesuada. Nam dignissim at nisl ac aliquet. In fermentum ' + |
31 |
'mauris et tellus fermentum rutrum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam hendrerit diam mauris, id rutrum justo malesuada nec. Duis ', |
32 |
'Ut fringilla enim nec augue rutrum, nec vestibulum orci sollicitudin. Donec eget ex tincidunt augue ullamcorper efficitur at sed odio. Praesent ac interdum elit. Suspendisse blandit feugiat pulvinar. ' |
33 |
]; |
34 |
this.pages = [ |
Error |
Row 35, Column 90: "Prefer textContent to $.text"
jquery/no-text
|
35 |
new Demo.SamplePage( 'page1', { label: 'Level 0', icon: 'window', level: 0, content: [ $( '<h3>' ).text( 'Page 1' ), lipsum[ 0 ] ] } ), |
Error |
Row 36, Column 90: "Prefer textContent to $.text"
jquery/no-text
|
36 |
new Demo.SamplePage( 'page2', { label: 'Level 1', icon: 'window', level: 1, content: [ $( '<h3>' ).text( 'Page 2' ), lipsum[ 1 ] ] } ), |
Error |
Row 37, Column 90: "Prefer textContent to $.text"
jquery/no-text
|
37 |
new Demo.SamplePage( 'page3', { label: 'Level 2', icon: 'window', level: 2, content: [ $( '<h3>' ).text( 'Page 3' ), lipsum[ 2 ] ] } ), |
Error |
Row 38, Column 90: "Prefer textContent to $.text"
jquery/no-text
|
38 |
new Demo.SamplePage( 'page4', { label: 'Level 1', icon: 'window', level: 1, content: [ $( '<h3>' ).text( 'Page 4' ), lipsum[ 3 ] ] } ), |
Error |
Row 39, Column 90: "Prefer textContent to $.text"
jquery/no-text
|
39 |
new Demo.SamplePage( 'page5', { label: 'Level 2', icon: 'window', level: 2, content: [ $( '<h3>' ).text( 'Page 5' ), lipsum[ 4 ] ] } ) |
40 |
]; |
41 |
|
42 |
this.bookletLayout.addPages( this.pages ); |
43 |
this.$body.append( this.bookletLayout.$element ); |
44 |
}; |
45 |
Demo.ContinuousOutlinedBookletDialog.prototype.getActionProcess = function ( action ) { |
46 |
if ( action ) { |
47 |
return new OO.ui.Process( function () { |
48 |
this.close( { action: action } ); |
49 |
}, this ); |
50 |
} |
51 |
return Demo.ContinuousOutlinedBookletDialog.parent.prototype.getActionProcess.call( this, action ); |
52 |
}; |
53 |
Demo.ContinuousOutlinedBookletDialog.prototype.getSetupProcess = function ( data ) { |
54 |
return Demo.ContinuousOutlinedBookletDialog.parent.prototype.getSetupProcess.call( this, data ) |
55 |
.next( function () { |
56 |
this.bookletLayout.setPage( 'page1' ); |
57 |
}, this ); |
58 |
}; |
59 |
Demo.ContinuousOutlinedBookletDialog.prototype.getTeardownProcess = function ( data ) { |
60 |
return Demo.ContinuousOutlinedBookletDialog.parent.prototype.getTeardownProcess.call( this, data ) |
61 |
.next( function () { |
62 |
this.bookletLayout.resetScroll(); |
63 |
}, this ); |
64 |
}; |
65 |
|
|
|
/demos/classes/DelayedReadyProcessDialog.js
|
1 problem (1 error, 0 warnings)
|
Line |
Source |
1 |
Demo.DelayedReadyProcessDialog = function DemoDelayedReadyProcessDialog( config ) { |
2 |
Demo.DelayedReadyProcessDialog.parent.call( this, config ); |
3 |
}; |
4 |
OO.inheritClass( Demo.DelayedReadyProcessDialog, Demo.SimpleDialog ); |
5 |
Demo.DelayedReadyProcessDialog.prototype.getReadyProcess = function () { |
6 |
return Demo.DelayedReadyProcessDialog.parent.prototype.getReadyProcess.call( this ).next( function () { |
Error |
Row 7, Column 18: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
7 |
var deferred = $.Deferred(); |
8 |
setTimeout( function () { |
9 |
deferred.resolve(); |
10 |
}, 2000 ); |
11 |
return deferred.promise(); |
12 |
} ); |
13 |
}; |
14 |
|
|
|
/demos/classes/DialogWithDropdowns.js
|
33 problems (33 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 35, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 40, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 45, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 49, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 53, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 58, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 63, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 67, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 71, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 73, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 78, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 78, Column 72: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 82, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 94, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 99, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 109, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 113, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 120, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 125, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 131, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 135, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 145, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 150, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 159, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 163, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 168, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 173, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 177, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 181, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 183, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 188, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 188, Column 76: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 208, Column 9: "Prefer textContent to $.text"
jquery/no-text
|
Line |
Source |
1 |
Demo.DialogWithDropdowns = function DemoDialogWithDropdowns( config ) { |
2 |
Demo.DialogWithDropdowns.parent.call( this, config ); |
3 |
}; |
4 |
OO.inheritClass( Demo.DialogWithDropdowns, OO.ui.ProcessDialog ); |
5 |
Demo.DialogWithDropdowns.static.title = 'Dialog with dropdowns ($overlay test)'; |
6 |
Demo.DialogWithDropdowns.static.actions = [ |
7 |
{ action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] }, |
8 |
{ action: 'cancel', label: 'Cancel', flags: [ 'safe', 'back' ] } |
9 |
]; |
10 |
Demo.DialogWithDropdowns.prototype.getBodyHeight = function () { |
11 |
return 300; |
12 |
}; |
13 |
Demo.DialogWithDropdowns.prototype.initialize = function () { |
14 |
var |
15 |
loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, ' + |
16 |
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\u200E', |
17 |
$spacer = $( '<div>' ).height( 350 ); |
18 |
Demo.DialogWithDropdowns.parent.prototype.initialize.apply( this, arguments ); |
19 |
this.bookletLayout = new OO.ui.BookletLayout( { |
20 |
outlined: true |
21 |
} ); |
22 |
this.pages = [ |
23 |
new Demo.SamplePage( 'info', { |
24 |
label: 'Information', |
25 |
icon: 'info', |
26 |
content: [ |
27 |
'This is a test of various kinds of dropdown menus and their $overlay config option. ', |
28 |
'Entries without any icon use a correctly set $overlay and their menus should be able to extend outside of this small dialog. ', |
29 |
'Entries with the ', new OO.ui.IconWidget( { icon: 'alert' } ), ' icon do not, and their menus should be clipped to the dialog\'s boundaries. ', |
30 |
'All dropdown menus should stick to the widget proper, even when scrolling while the menu is open.' |
31 |
] |
32 |
} ), |
33 |
new Demo.SamplePage( 'dropdown', { |
34 |
label: 'DropdownWidget', |
Error |
Row 35, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
35 |
content: [ $spacer.clone(), new OO.ui.DropdownWidget( { |
36 |
$overlay: this.$overlay, |
37 |
menu: { |
38 |
items: this.makeItems() |
39 |
} |
Error |
Row 40, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
40 |
} ), $spacer.clone() ] |
41 |
} ), |
42 |
new Demo.SamplePage( 'dropdown2', { |
43 |
label: 'DropdownWidget', |
44 |
icon: 'alert', |
Error |
Row 45, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
45 |
content: [ $spacer.clone(), new OO.ui.DropdownWidget( { |
46 |
menu: { |
47 |
items: this.makeItems() |
48 |
} |
Error |
Row 49, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
49 |
} ), $spacer.clone() ] |
50 |
} ), |
51 |
new Demo.SamplePage( 'combobox', { |
52 |
label: 'ComboBoxInputWidget', |
Error |
Row 53, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
53 |
content: [ $spacer.clone(), new OO.ui.ComboBoxInputWidget( { |
54 |
$overlay: this.$overlay, |
55 |
menu: { |
56 |
items: this.makeItems() |
57 |
} |
Error |
Row 58, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
58 |
} ), $spacer.clone() ] |
59 |
} ), |
60 |
new Demo.SamplePage( 'combobox2', { |
61 |
label: 'ComboBoxInputWidget', |
62 |
icon: 'alert', |
Error |
Row 63, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
63 |
content: [ $spacer.clone(), new OO.ui.ComboBoxInputWidget( { |
64 |
menu: { |
65 |
items: this.makeItems() |
66 |
} |
Error |
Row 67, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
67 |
} ), $spacer.clone() ] |
68 |
} ), |
69 |
new Demo.SamplePage( 'lookup', { |
70 |
label: 'LookupElement', |
Error |
Row 71, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
71 |
content: [ $spacer.clone(), new Demo.NumberLookupTextInputWidget( { |
72 |
$overlay: this.$overlay |
Error |
Row 73, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
73 |
} ), $spacer.clone() ] |
74 |
} ), |
75 |
new Demo.SamplePage( 'lookup2', { |
76 |
label: 'LookupElement', |
77 |
icon: 'alert', |
Error |
Row 78, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 78, Column 72: "Prefer cloneNode to $.clone"
jquery/no-clone
|
78 |
content: [ $spacer.clone(), new Demo.NumberLookupTextInputWidget(), $spacer.clone() ] |
79 |
} ), |
80 |
new Demo.SamplePage( 'fieldsetandfield', { |
81 |
label: 'FieldsetLayout and FieldLayout', |
Error |
Row 82, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
82 |
content: [ $spacer.clone(), new OO.ui.FieldsetLayout( { |
83 |
$overlay: this.$overlay, |
84 |
label: 'Fieldset', |
85 |
help: loremIpsum, |
86 |
items: [ |
87 |
new OO.ui.FieldLayout( new OO.ui.CheckboxInputWidget(), { |
88 |
$overlay: this.$overlay, |
89 |
align: 'inline', |
90 |
label: 'Field', |
91 |
help: loremIpsum |
92 |
} ) |
93 |
] |
Error |
Row 94, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
94 |
} ), $spacer.clone() ] |
95 |
} ), |
96 |
new Demo.SamplePage( 'fieldsetandfield2', { |
97 |
label: 'FieldsetLayout and FieldLayout', |
98 |
icon: 'alert', |
Error |
Row 99, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
99 |
content: [ $spacer.clone(), new OO.ui.FieldsetLayout( { |
100 |
label: 'Fieldset', |
101 |
help: loremIpsum, |
102 |
items: [ |
103 |
new OO.ui.FieldLayout( new OO.ui.CheckboxInputWidget(), { |
104 |
align: 'inline', |
105 |
label: 'Field', |
106 |
help: loremIpsum |
107 |
} ) |
108 |
] |
Error |
Row 109, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
109 |
} ), $spacer.clone() ] |
110 |
} ), |
111 |
new Demo.SamplePage( 'popupbutton', { |
112 |
label: 'PopupButtonWidget', |
Error |
Row 113, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
113 |
content: [ $spacer.clone(), new OO.ui.PopupButtonWidget( { |
114 |
$overlay: this.$overlay, |
115 |
label: 'Popup button', |
116 |
popup: { |
117 |
padded: true, |
118 |
$content: this.makeContents() |
119 |
} |
Error |
Row 120, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
120 |
} ), $spacer.clone() ] |
121 |
} ), |
122 |
new Demo.SamplePage( 'popupbutton2', { |
123 |
label: 'PopupButtonWidget', |
124 |
icon: 'alert', |
Error |
Row 125, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
125 |
content: [ $spacer.clone(), new OO.ui.PopupButtonWidget( { |
126 |
label: 'Popup button', |
127 |
popup: { |
128 |
padded: true, |
129 |
$content: this.makeContents() |
130 |
} |
Error |
Row 131, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
131 |
} ), $spacer.clone() ] |
132 |
} ), |
133 |
new Demo.SamplePage( 'popupbuttonhoriz', { |
134 |
label: 'PopupButtonWidget (horizontal)', |
Error |
Row 135, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
135 |
content: [ $spacer.clone(), new OO.ui.PopupButtonWidget( { |
136 |
$overlay: this.$overlay, |
137 |
label: 'Popup button', |
138 |
popup: { |
139 |
position: 'after', |
140 |
height: 200, |
141 |
width: 200, |
142 |
padded: true, |
143 |
$content: this.makeContents() |
144 |
} |
Error |
Row 145, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
145 |
} ), $spacer.clone() ] |
146 |
} ), |
147 |
new Demo.SamplePage( 'popupbuttonhoriz2', { |
148 |
label: 'PopupButtonWidget (horizontal)', |
149 |
icon: 'alert', |
Error |
Row 150, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
150 |
content: [ $spacer.clone(), new OO.ui.PopupButtonWidget( { |
151 |
label: 'Popup button', |
152 |
popup: { |
153 |
position: 'after', |
154 |
height: 200, |
155 |
width: 200, |
156 |
padded: true, |
157 |
$content: this.makeContents() |
158 |
} |
Error |
Row 159, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
159 |
} ), $spacer.clone() ] |
160 |
} ), |
161 |
new Demo.SamplePage( 'tagmenu', { |
162 |
label: 'TagMultiselectWidget (menu)', |
Error |
Row 163, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
163 |
content: [ $spacer.clone(), new OO.ui.MenuTagMultiselectWidget( { |
164 |
$overlay: this.$overlay, |
165 |
menu: { |
166 |
items: this.makeItems() |
167 |
} |
Error |
Row 168, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
168 |
} ), $spacer.clone() ] |
169 |
} ), |
170 |
new Demo.SamplePage( 'tagmenu2', { |
171 |
label: 'TagMultiselectWidget (menu)', |
172 |
icon: 'alert', |
Error |
Row 173, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
173 |
content: [ $spacer.clone(), new OO.ui.MenuTagMultiselectWidget( { |
174 |
menu: { |
175 |
items: this.makeItems() |
176 |
} |
Error |
Row 177, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
177 |
} ), $spacer.clone() ] |
178 |
} ), |
179 |
new Demo.SamplePage( 'tagpopup', { |
180 |
label: 'TagMultiselectWidget (popup)', |
Error |
Row 181, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
181 |
content: [ $spacer.clone(), new Demo.TagNumberPopupMultiselectWidget( { |
182 |
$overlay: this.$overlay |
Error |
Row 183, Column 9: "Prefer cloneNode to $.clone"
jquery/no-clone
|
183 |
} ), $spacer.clone() ] |
184 |
} ), |
185 |
new Demo.SamplePage( 'tagpopup2', { |
186 |
label: 'TagMultiselectWidget (popup)', |
187 |
icon: 'alert', |
Error |
Row 188, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 188, Column 76: "Prefer cloneNode to $.clone"
jquery/no-clone
|
188 |
content: [ $spacer.clone(), new Demo.TagNumberPopupMultiselectWidget(), $spacer.clone() ] |
189 |
} ) |
190 |
]; |
191 |
this.bookletLayout.on( 'set', function ( page ) { |
192 |
page.$element[ 0 ].scrollTop = 325; |
193 |
} ); |
194 |
this.bookletLayout.addPages( this.pages ); |
195 |
this.$body.append( this.bookletLayout.$element ); |
196 |
}; |
197 |
Demo.DialogWithDropdowns.prototype.makeItems = function () { |
198 |
return [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ].map( function ( val ) { |
199 |
return new OO.ui.MenuOptionWidget( { |
200 |
data: val, |
201 |
label: String( val ) |
202 |
} ); |
203 |
} ); |
204 |
}; |
205 |
Demo.DialogWithDropdowns.prototype.makeContents = function () { |
206 |
var loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, ' + |
207 |
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\u200E'; |
Error |
Row 208, Column 9: "Prefer textContent to $.text"
jquery/no-text
|
208 |
return $( '<p>' ).text( loremIpsum ); |
209 |
}; |
210 |
|
211 |
Demo.DialogWithDropdowns.prototype.getActionProcess = function ( action ) { |
212 |
if ( action ) { |
213 |
return new OO.ui.Process( function () { |
214 |
this.close( { action: action } ); |
215 |
}, this ); |
216 |
} |
217 |
return Demo.DialogWithDropdowns.parent.prototype.getActionProcess.call( this, action ); |
218 |
}; |
219 |
|
|
|
/demos/classes/DialogWithPopupAndDropdown.js
|
2 problems (2 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 30, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Error |
Row 38, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
Line |
Source |
1 |
Demo.DialogWithPopupAndDropdown = function DemoDialogWithPopupAndDropdown( config ) { |
2 |
Demo.DialogWithPopupAndDropdown.parent.call( this, config ); |
3 |
}; |
4 |
OO.inheritClass( Demo.DialogWithPopupAndDropdown, OO.ui.ProcessDialog ); |
5 |
Demo.DialogWithPopupAndDropdown.static.title = 'Dialog with popup and dropdown (ClippableElement test)'; |
6 |
Demo.DialogWithPopupAndDropdown.static.actions = [ |
7 |
{ action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] }, |
8 |
{ action: 'cancel', label: 'Cancel', flags: [ 'safe', 'back' ] } |
9 |
]; |
10 |
Demo.DialogWithPopupAndDropdown.prototype.getBodyHeight = function () { |
11 |
return 300; |
12 |
}; |
13 |
Demo.DialogWithPopupAndDropdown.prototype.initialize = function () { |
14 |
var $spacer = $( '<div>' ).height( 240 ); |
15 |
Demo.DialogWithPopupAndDropdown.parent.prototype.initialize.apply( this, arguments ); |
16 |
this.bookletLayout = new OO.ui.BookletLayout( { |
17 |
outlined: true |
18 |
} ); |
19 |
this.pages = [ |
20 |
new Demo.SamplePage( 'info', { |
21 |
label: 'Information', |
22 |
icon: 'info', |
23 |
content: [ |
24 |
'Widgets that don\'t use $overlay get clipped at the bottom of their container. ', |
25 |
'This is a test of two such cases' |
26 |
] |
27 |
} ), |
28 |
new Demo.SamplePage( 'dropdownbottom', { |
29 |
label: 'DropdownWidget at bottom', |
Error |
Row 30, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
30 |
content: [ $spacer.clone(), new OO.ui.DropdownWidget( { |
31 |
menu: { |
32 |
items: this.makeItems() |
33 |
} |
34 |
} ) ] |
35 |
} ), |
36 |
new Demo.SamplePage( 'popupbottom', { |
37 |
label: 'Popup at bottom', |
Error |
Row 38, Column 15: "Prefer cloneNode to $.clone"
jquery/no-clone
|
38 |
content: [ $spacer.clone(), new OO.ui.PopupButtonWidget( { |
39 |
icon: 'info', |
40 |
label: 'Popup here', |
41 |
framed: false, |
42 |
popup: { |
43 |
head: true, |
44 |
label: 'More information', |
45 |
$content: $( '<p>Extra information here.</p>' ), |
46 |
padded: true |
47 |
} |
48 |
} ) ] |
49 |
} ) |
50 |
]; |
51 |
this.bookletLayout.addPages( this.pages ); |
52 |
this.$body.append( this.bookletLayout.$element ); |
53 |
}; |
54 |
Demo.DialogWithPopupAndDropdown.prototype.makeItems = function () { |
55 |
return [ 0, 1, 2, 3, 4 ].map( function ( val ) { |
56 |
return new OO.ui.MenuOptionWidget( { |
57 |
data: val, |
58 |
label: String( val ) |
59 |
} ); |
60 |
} ); |
61 |
}; |
62 |
|
63 |
Demo.DialogWithPopupAndDropdown.prototype.getActionProcess = function ( action ) { |
64 |
if ( action ) { |
65 |
return new OO.ui.Process( function () { |
66 |
this.close( { action: action } ); |
67 |
}, this ); |
68 |
} |
69 |
return Demo.DialogWithPopupAndDropdown.parent.prototype.getActionProcess.call( this, action ); |
70 |
}; |
71 |
|
|
|
/demos/classes/DraggableGroupWidget.js
|
0 problems
|
|
/demos/classes/DraggableHandledItemWidget.js
|
0 problems
|
|
/demos/classes/DraggableItemWidget.js
|
0 problems
|
|
/demos/classes/DynamicLabelTextInputWidget.js
|
0 problems
|
|
/demos/classes/FailedReadyProcessDialog.js
|
1 problem (1 error, 0 warnings)
|
Line |
Source |
1 |
Demo.FailedReadyProcessDialog = function DemoFailedReadyProcessDialog( config ) { |
2 |
Demo.FailedReadyProcessDialog.parent.call( this, config ); |
3 |
}; |
4 |
OO.inheritClass( Demo.FailedReadyProcessDialog, Demo.SimpleDialog ); |
5 |
Demo.FailedReadyProcessDialog.prototype.getReadyProcess = function () { |
6 |
return Demo.FailedReadyProcessDialog.parent.prototype.getReadyProcess.call( this ).next( function () { |
Error |
Row 7, Column 10: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
7 |
return $.Deferred().reject().promise(); |
8 |
} ); |
9 |
}; |
10 |
|
|
|
/demos/classes/FailedSetupProcessDialog.js
|
1 problem (1 error, 0 warnings)
|
Line |
Source |
1 |
Demo.FailedSetupProcessDialog = function DemoFailedSetupProcessDialog( config ) { |
2 |
Demo.FailedSetupProcessDialog.parent.call( this, config ); |
3 |
}; |
4 |
OO.inheritClass( Demo.FailedSetupProcessDialog, Demo.SimpleDialog ); |
5 |
Demo.FailedSetupProcessDialog.prototype.getSetupProcess = function () { |
6 |
return Demo.FailedSetupProcessDialog.parent.prototype.getSetupProcess.call( this ).next( function () { |
Error |
Row 7, Column 10: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
7 |
return $.Deferred().reject().promise(); |
8 |
} ); |
9 |
}; |
10 |
|
|
|
/demos/classes/FloatableTest.js
|
2 problems (2 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 24, Column 20: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 29, Column 29: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Line |
Source |
1 |
Demo.FloatableTest = function DemoFloatableTest( config ) { |
2 |
// Parent constructor |
3 |
Demo.FloatableTest.parent.call( this, config ); |
4 |
// Properties |
5 |
// Must be equal to dialog width and height |
6 |
this.viewportSize = 500; |
7 |
// Width and height of scrollable area |
8 |
this.outerSize = 1000; |
9 |
// Width and height of scrollable container |
10 |
this.innerSize = 400; |
11 |
}; |
12 |
OO.inheritClass( Demo.FloatableTest, OO.ui.ProcessDialog ); |
13 |
Demo.FloatableTest.static.title = 'FloatableElement test'; |
14 |
Demo.FloatableTest.static.actions = [ |
15 |
{ action: '', label: 'Cancel', flags: [ 'safe', 'back' ] }, |
16 |
{ action: 'center', label: 'Center view' } |
17 |
]; |
18 |
Demo.FloatableTest.prototype.getBodyHeight = function () { |
19 |
return this.viewportSize; |
20 |
}; |
21 |
Demo.FloatableTest.prototype.initialize = function () { |
22 |
Demo.FloatableTest.parent.prototype.initialize.apply( this, arguments ); |
23 |
|
Error |
Row 24, Column 20: "Prefer getComputedStyle to $.css"
jquery/no-css
|
24 |
this.$container = $( '<div>' ).css( { width: this.outerSize, height: this.outerSize } ); |
25 |
this.selectWidget = new Demo.PositionSelectWidget(); |
26 |
this.toggleOverlayWidget = new OO.ui.ToggleSwitchWidget( { value: true } ); |
27 |
this.toggleClippingWidget = new OO.ui.ToggleSwitchWidget( { value: false } ); |
28 |
|
Error |
Row 29, Column 29: "Prefer getComputedStyle to $.css"
jquery/no-css
|
29 |
this.$floatableContainer = $( '<div>' ).css( { width: this.innerSize, height: this.innerSize } ); |
30 |
this.floatable = new Demo.FloatableWidget( { $floatableContainer: this.$floatableContainer } ); |
31 |
this.floatable.toggle( false ); |
32 |
|
33 |
this.floatable.$element.addClass( 'demo-floatableTest-floatable' ); |
34 |
this.$floatableContainer.addClass( 'demo-floatableTest-container' ); |
35 |
|
36 |
this.selectWidget.connect( this, { select: 'onSelectPosition' } ); |
37 |
this.toggleOverlayWidget.connect( this, { change: 'onToggleOverlay' } ); |
38 |
this.toggleClippingWidget.connect( this, { change: 'onToggleClipping' } ); |
39 |
|
40 |
this.fieldset = new OO.ui.FieldsetLayout( { |
41 |
items: [ |
42 |
new OO.ui.FieldLayout( this.selectWidget, { |
43 |
align: 'top', |
44 |
label: 'Floatable position' |
45 |
} ), |
46 |
new OO.ui.FieldLayout( this.toggleClippingWidget, { |
47 |
align: 'inline', |
48 |
label: 'Enable clipping' |
49 |
} ), |
50 |
new OO.ui.FieldLayout( this.toggleOverlayWidget, { |
51 |
align: 'inline', |
52 |
label: 'Use overlay' |
53 |
} ) |
54 |
] |
55 |
} ); |
56 |
|
57 |
this.$body.append( |
58 |
this.$container.append( |
59 |
this.$floatableContainer.append( this.fieldset.$element ) |
60 |
) |
61 |
); |
62 |
this.$overlay.append( this.floatable.$element ); |
63 |
}; |
64 |
Demo.FloatableTest.prototype.onSelectPosition = function ( option ) { |
65 |
this.floatable.setHorizontalPosition( option.getData().horizontalPosition ); |
66 |
this.floatable.setVerticalPosition( option.getData().verticalPosition ); |
67 |
}; |
68 |
Demo.FloatableTest.prototype.onToggleOverlay = function ( useOverlay ) { |
69 |
this.floatable.togglePositioning( false ); |
70 |
this.floatable.toggleClipping( false ); |
71 |
( useOverlay ? this.$overlay : this.$container ).append( this.floatable.$element ); |
72 |
this.floatable.togglePositioning( true ); |
73 |
this.floatable.toggleClipping( this.toggleClippingWidget.getValue() ); |
74 |
}; |
75 |
Demo.FloatableTest.prototype.onToggleClipping = function ( useClipping ) { |
76 |
this.floatable.toggleClipping( useClipping ); |
77 |
}; |
78 |
Demo.FloatableTest.prototype.centerView = function () { |
79 |
var offset = ( this.outerSize - this.viewportSize ) / 2; |
80 |
this.$body[ 0 ].scrollTop = offset; |
81 |
// Different browsers count scrollLeft in RTL differently, |
82 |
// see <https://github.com/othree/jquery.rtl-scroll-type>. |
83 |
// This isn't correct for arbitrary offsets, but works for centering. |
84 |
this.$body[ 0 ].scrollLeft = offset; |
85 |
if ( this.$body[ 0 ].scrollLeft === 0 ) { |
86 |
this.$body[ 0 ].scrollLeft = -offset; |
87 |
} |
88 |
}; |
89 |
Demo.FloatableTest.prototype.getSetupProcess = function () { |
90 |
return Demo.FloatableTest.parent.prototype.getSetupProcess.call( this ).next( function () { |
91 |
var offset, side; |
92 |
offset = ( this.outerSize - this.innerSize ) / 2; |
93 |
side = this.getDir() === 'rtl' ? 'right' : 'left'; |
94 |
this.$floatableContainer.css( 'top', offset ); |
95 |
this.$floatableContainer.css( side, offset ); |
96 |
|
97 |
this.selectWidget.selectItemByData( { |
98 |
horizontalPosition: 'start', |
99 |
verticalPosition: 'below' |
100 |
} ); |
101 |
}, this ); |
102 |
}; |
103 |
Demo.FloatableTest.prototype.getReadyProcess = function () { |
104 |
return new OO.ui.Process( function () { |
105 |
this.centerView(); |
106 |
this.floatable.toggle( true ); |
107 |
this.floatable.togglePositioning( true ); |
108 |
this.floatable.toggleClipping( this.toggleClippingWidget.getValue() ); |
109 |
}, this ); |
110 |
}; |
111 |
Demo.FloatableTest.prototype.getHoldProcess = function () { |
112 |
return new OO.ui.Process( function () { |
113 |
this.floatable.toggleClipping( false ); |
114 |
this.floatable.togglePositioning( false ); |
115 |
this.floatable.toggle( false ); |
116 |
}, this ); |
117 |
}; |
118 |
Demo.FloatableTest.prototype.getActionProcess = function ( action ) { |
119 |
if ( action === 'center' ) { |
120 |
return new OO.ui.Process( function () { |
121 |
this.centerView(); |
122 |
}, this ); |
123 |
} |
124 |
return Demo.FloatableTest.parent.prototype.getActionProcess.call( this, action ); |
125 |
}; |
126 |
|
|
|
/demos/classes/FloatableWidget.js
|
0 problems
|
|
/demos/classes/IndexedDialog.js
|
9 problems (9 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 26, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 27, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 28, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 29, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 30, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 31, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 32, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 33, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 34, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Line |
Source |
1 |
Demo.IndexedDialog = function DemoIndexedDialog( config ) { |
2 |
Demo.IndexedDialog.parent.call( this, config ); |
3 |
}; |
4 |
OO.inheritClass( Demo.IndexedDialog, OO.ui.ProcessDialog ); |
5 |
Demo.IndexedDialog.static.title = 'Indexed dialog'; |
6 |
Demo.IndexedDialog.static.actions = [ |
7 |
{ action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] }, |
8 |
{ action: 'cancel', label: 'Cancel', flags: [ 'safe', 'back' ] } |
9 |
]; |
10 |
Demo.IndexedDialog.prototype.getBodyHeight = function () { |
11 |
return 250; |
12 |
}; |
13 |
Demo.IndexedDialog.prototype.initialize = function () { |
14 |
var loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, ' + |
15 |
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\u200E'; |
16 |
Demo.IndexedDialog.parent.prototype.initialize.apply( this, arguments ); |
17 |
this.indexLayout = new OO.ui.IndexLayout(); |
18 |
this.tabPanels = [ |
19 |
new Demo.SampleTabPanel( 'first', { label: 'One tab' } ), |
20 |
new Demo.SampleTabPanel( 'second', { label: 'Two tab' } ), |
21 |
new Demo.SampleTabPanel( 'third', { label: 'Three tab' } ), |
22 |
new Demo.SampleTabPanel( 'fourth', { label: 'Four tab' } ), |
23 |
new Demo.SampleTabPanel( 'long', { |
24 |
label: 'Long tab', |
25 |
content: [ |
Error |
Row 26, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
26 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 27, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
27 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 28, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
28 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 29, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
29 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 30, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
30 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 31, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
31 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 32, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
32 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 33, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
33 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 34, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
34 |
$( '<p>' ).text( loremIpsum ) |
35 |
] |
36 |
} ) |
37 |
]; |
38 |
|
39 |
this.indexLayout.addTabPanels( this.tabPanels ); |
40 |
this.$body.append( this.indexLayout.$element ); |
41 |
|
42 |
this.indexLayout.getTabs().findItemFromData( 'fourth' ).setDisabled( true ); |
43 |
}; |
44 |
Demo.IndexedDialog.prototype.getActionProcess = function ( action ) { |
45 |
if ( action ) { |
46 |
return new OO.ui.Process( function () { |
47 |
this.close( { action: action } ); |
48 |
}, this ); |
49 |
} |
50 |
return Demo.IndexedDialog.parent.prototype.getActionProcess.call( this, action ); |
51 |
}; |
52 |
Demo.IndexedDialog.prototype.getTeardownProcess = function ( data ) { |
53 |
return Demo.IndexedDialog.parent.prototype.getTeardownProcess.call( this, data ) |
54 |
.next( function () { |
55 |
this.indexLayout.resetScroll(); |
56 |
}, this ); |
57 |
}; |
58 |
|
|
|
/demos/classes/LongProcessDialog.js
|
0 problems
|
|
/demos/classes/NumberLookupTextInputWidget.js
|
1 problem (1 error, 0 warnings)
|
Line |
Source |
1 |
/** |
2 |
* Demo for LookupElement. |
3 |
* |
4 |
* @class |
5 |
* @extends OO.ui.TextInputWidget |
6 |
* @mixins OO.ui.mixin.LookupElement |
7 |
* |
8 |
* @constructor |
9 |
* @param {Object} config Configuration options |
10 |
*/ |
11 |
Demo.NumberLookupTextInputWidget = function DemoNumberLookupTextInputWidget( config ) { |
12 |
// Parent constructor |
13 |
OO.ui.TextInputWidget.call( this, $.extend( { validate: 'integer' }, config ) ); |
14 |
// Mixin constructors |
15 |
OO.ui.mixin.LookupElement.call( this, config ); |
16 |
}; |
17 |
OO.inheritClass( Demo.NumberLookupTextInputWidget, OO.ui.TextInputWidget ); |
18 |
OO.mixinClass( Demo.NumberLookupTextInputWidget, OO.ui.mixin.LookupElement ); |
19 |
|
20 |
/** |
21 |
* @inheritdoc |
22 |
*/ |
23 |
Demo.NumberLookupTextInputWidget.prototype.getLookupRequest = function () { |
24 |
var |
25 |
value = this.getValue(), |
Error |
Row 26, Column 14: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
26 |
deferred = $.Deferred(), |
27 |
delay = 500 + Math.floor( Math.random() * 500 ); |
28 |
|
29 |
this.getValidity().then( function () { |
30 |
// Resolve with results after a faked delay |
31 |
setTimeout( function () { |
32 |
deferred.resolve( [ |
33 |
value * 1, value * 2, value * 3, value * 4, value * 5, |
34 |
value * 6, value * 7, value * 8, value * 9, value * 10 |
35 |
] ); |
36 |
}, delay ); |
37 |
}, function () { |
38 |
// No results when the input contains invalid content |
39 |
deferred.resolve( [] ); |
40 |
} ); |
41 |
|
42 |
return deferred.promise( { abort: function () {} } ); |
43 |
}; |
44 |
|
45 |
/** |
46 |
* @inheritdoc |
47 |
*/ |
48 |
Demo.NumberLookupTextInputWidget.prototype.getLookupCacheDataFromResponse = function ( response ) { |
49 |
return response || []; |
50 |
}; |
51 |
|
52 |
/** |
53 |
* @inheritdoc |
54 |
*/ |
55 |
Demo.NumberLookupTextInputWidget.prototype.getLookupMenuOptionsFromData = function ( data ) { |
56 |
var |
57 |
items = [], |
58 |
i, number; |
59 |
for ( i = 0; i < data.length; i++ ) { |
60 |
number = String( data[ i ] ); |
61 |
items.push( new OO.ui.MenuOptionWidget( { |
62 |
data: number, |
63 |
label: number |
64 |
} ) ); |
65 |
} |
66 |
|
67 |
return items; |
68 |
}; |
69 |
|
|
|
/demos/classes/OutlinedBookletDialog.js
|
0 problems
|
Severity |
Rule |
Error |
Row 18, Column 17: "Prefer getAttribute to $.attr"
jquery/no-attr
|
Error |
Row 54, Column 9: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 55, Column 9: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 56, Column 9: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 75, Column 4: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 79, Column 4: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 83, Column 4: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Line |
Source |
1 |
Demo.PopupButtonWidgetTest = function DemoPopupButtonWidgetTest( config ) { |
2 |
Demo.PopupButtonWidgetTest.parent.call( this, config ); |
3 |
}; |
4 |
OO.inheritClass( Demo.PopupButtonWidgetTest, OO.ui.ProcessDialog ); |
5 |
Demo.PopupButtonWidgetTest.static.title = 'PopupButtonWidget test'; |
6 |
Demo.PopupButtonWidgetTest.static.actions = [ |
7 |
{ action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] }, |
8 |
{ action: 'cancel', label: 'Cancel', flags: [ 'safe', 'back' ] } |
9 |
]; |
10 |
Demo.PopupButtonWidgetTest.prototype.initialize = function () { |
11 |
Demo.PopupButtonWidgetTest.parent.prototype.initialize.apply( this, arguments ); |
12 |
|
13 |
this.panel = new OO.ui.PanelLayout( { |
14 |
expanded: false, |
15 |
padded: true |
16 |
} ); |
17 |
|
Error |
Row 18, Column 17: "Prefer getAttribute to $.attr"
jquery/no-attr
|
18 |
this.$center = $( '<td>' ).attr( { colspan: 3, rowspan: 3 } ); |
19 |
|
20 |
this.toggleOverlayWidget = new OO.ui.ToggleSwitchWidget( { value: true } ); |
21 |
this.toggleAnchorWidget = new OO.ui.ToggleSwitchWidget( { value: true } ); |
22 |
this.showAllWidget = new OO.ui.ButtonWidget( { label: 'Toggle all' } ); |
23 |
|
24 |
this.toggleOverlayWidget.connect( this, { change: 'makeTable' } ); |
25 |
this.toggleAnchorWidget.connect( this, { change: 'makeTable' } ); |
26 |
this.showAllWidget.connect( this, { click: 'toggleAll' } ); |
27 |
|
28 |
this.fieldset = new OO.ui.FieldsetLayout( { |
29 |
items: [ |
30 |
new OO.ui.FieldLayout( this.toggleAnchorWidget, { |
31 |
align: 'inline', |
32 |
label: 'Enable anchors' |
33 |
} ), |
34 |
new OO.ui.FieldLayout( this.toggleOverlayWidget, { |
35 |
align: 'inline', |
36 |
label: 'Use overlay' |
37 |
} ), |
38 |
new OO.ui.FieldLayout( this.showAllWidget, { |
39 |
align: 'top' |
40 |
} ) |
41 |
] |
42 |
} ); |
43 |
|
44 |
this.$center.append( this.fieldset.$element ); |
45 |
|
46 |
this.makeTable(); |
47 |
|
48 |
this.$body.append( this.panel.$element ); |
49 |
}; |
50 |
Demo.PopupButtonWidgetTest.prototype.makeContents = function () { |
51 |
var loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, ' + |
52 |
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\u200E'; |
53 |
return $( [] ) |
Error |
Row 54, Column 9: "Prefer textContent to $.text"
jquery/no-text
|
54 |
.add( $( '<p>' ).text( loremIpsum ) ) |
Error |
Row 55, Column 9: "Prefer textContent to $.text"
jquery/no-text
|
55 |
.add( $( '<p>' ).text( loremIpsum ) ) |
Error |
Row 56, Column 9: "Prefer textContent to $.text"
jquery/no-text
|
56 |
.add( $( '<p>' ).text( loremIpsum ) ); |
57 |
}; |
58 |
Demo.PopupButtonWidgetTest.prototype.makeTable = function () { |
59 |
this.buttons = []; |
60 |
if ( this.$table ) { |
61 |
this.$table.detach(); |
62 |
} |
63 |
|
64 |
this.$table = $( '<table>' ).append( |
65 |
$( '<tr>' ).append( |
66 |
$( '<td>' ), |
67 |
$( '<td>' ).append( this.getButton( 'above', 'backwards' ).$element ), |
68 |
$( '<td>' ).append( this.getButton( 'above', 'center' ).$element ), |
69 |
$( '<td>' ).append( this.getButton( 'above', 'forwards' ).$element ), |
70 |
$( '<td>' ) |
71 |
), |
72 |
$( '<tr>' ).append( |
73 |
$( '<td>' ).append( this.getButton( 'before', 'backwards' ).$element ), |
74 |
this.$center, |
Error |
Row 75, Column 4: "Prefer getComputedStyle to $.css"
jquery/no-css
|
75 |
$( '<td>' ).append( this.getButton( 'after', 'backwards' ).$element ).css( 'text-align', this.getDir() === 'rtl' ? 'left' : 'right' ) |
76 |
), |
77 |
$( '<tr>' ).append( |
78 |
$( '<td>' ).append( this.getButton( 'before', 'center' ).$element ), |
Error |
Row 79, Column 4: "Prefer getComputedStyle to $.css"
jquery/no-css
|
79 |
$( '<td>' ).append( this.getButton( 'after', 'center' ).$element ).css( 'text-align', this.getDir() === 'rtl' ? 'left' : 'right' ) |
80 |
), |
81 |
$( '<tr>' ).append( |
82 |
$( '<td>' ).append( this.getButton( 'before', 'forwards' ).$element ), |
Error |
Row 83, Column 4: "Prefer getComputedStyle to $.css"
jquery/no-css
|
83 |
$( '<td>' ).append( this.getButton( 'after', 'forwards' ).$element ).css( 'text-align', this.getDir() === 'rtl' ? 'left' : 'right' ) |
84 |
), |
85 |
$( '<tr>' ).append( |
86 |
$( '<td>' ), |
87 |
$( '<td>' ).append( this.getButton( 'below', 'backwards' ).$element ), |
88 |
$( '<td>' ).append( this.getButton( 'below', 'center' ).$element ), |
89 |
$( '<td>' ).append( this.getButton( 'below', 'forwards' ).$element ), |
90 |
$( '<td>' ) |
91 |
) |
92 |
); |
93 |
this.panel.$element.append( this.$table ); |
94 |
}; |
95 |
Demo.PopupButtonWidgetTest.prototype.getButton = function ( position, align ) { |
96 |
var button = new OO.ui.PopupButtonWidget( { |
97 |
$overlay: ( this.toggleOverlayWidget.getValue() ? this.$overlay : null ), |
98 |
label: $( '<span>' ).append( position + '<br>' + align ), |
99 |
popup: { |
100 |
position: position, |
101 |
align: align, |
102 |
anchor: this.toggleAnchorWidget.getValue(), |
103 |
padded: true, |
104 |
$content: this.makeContents() |
105 |
} |
106 |
} ); |
107 |
|
108 |
this.buttons.push( button ); |
109 |
return button; |
110 |
}; |
111 |
Demo.PopupButtonWidgetTest.prototype.toggleAll = function () { |
112 |
this.buttons.forEach( function ( button ) { |
113 |
button.getPopup().toggle(); |
114 |
} ); |
115 |
}; |
116 |
Demo.PopupButtonWidgetTest.prototype.getActionProcess = function ( action ) { |
117 |
if ( action ) { |
118 |
return new OO.ui.Process( function () { |
119 |
this.close( { action: action } ); |
120 |
}, this ); |
121 |
} |
122 |
return Demo.PopupButtonWidgetTest.parent.prototype.getActionProcess.call( this, action ); |
123 |
}; |
124 |
|
|
|
/demos/classes/PositionSelectWidget.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 16, Column 4: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Line |
Source |
1 |
Demo.PositionSelectWidget = function DemoPositionSelectWidget( config ) { |
2 |
var verticalPositions, horizontalPositions, $table, |
3 |
widget = this; |
4 |
|
5 |
Demo.PositionSelectWidget.parent.call( this, config ); |
6 |
|
7 |
verticalPositions = [ 'above', 'top', 'center', 'bottom', 'below' ]; |
8 |
horizontalPositions = [ 'before', 'start', 'center', 'end', 'after' ]; |
9 |
|
10 |
$table = $( '<table>' ); |
11 |
verticalPositions.forEach( function ( v ) { |
12 |
var $tr = $( '<tr>' ); |
13 |
horizontalPositions.forEach( function ( h ) { |
14 |
var $td = $( '<td>' ); |
15 |
$td.append( widget.getOption( h, v ).$element ); |
Error |
Row 16, Column 4: "Prefer setAttribute to $.attr"
jquery/no-attr
|
16 |
$td.attr( 'title', v + '/' + h ); |
17 |
$tr.append( $td ); |
18 |
} ); |
19 |
$table.append( $tr ); |
20 |
} ); |
21 |
|
22 |
this.$element.append( $table ); |
23 |
this.$element.addClass( 'demo-positionSelectWidget' ); |
24 |
}; |
25 |
OO.inheritClass( Demo.PositionSelectWidget, OO.ui.RadioSelectWidget ); |
26 |
Demo.PositionSelectWidget.prototype.getOption = function ( h, v ) { |
27 |
var option = new OO.ui.RadioOptionWidget( { |
28 |
data: { |
29 |
horizontalPosition: h, |
30 |
verticalPosition: v |
31 |
} |
32 |
} ); |
33 |
this.addItems( [ option ] ); |
34 |
return option; |
35 |
}; |
36 |
|
|
|
/demos/classes/ProcessDialog.js
|
2 problems (2 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 16, Column 3: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 17, Column 3: "Prefer textContent to $.text"
jquery/no-text
|
Line |
Source |
1 |
Demo.ProcessDialog = function DemoProcessDialog( config ) { |
2 |
Demo.ProcessDialog.parent.call( this, config ); |
3 |
}; |
4 |
OO.inheritClass( Demo.ProcessDialog, OO.ui.ProcessDialog ); |
5 |
Demo.ProcessDialog.static.title = 'Process dialog'; |
6 |
Demo.ProcessDialog.static.actions = [ |
7 |
{ action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] }, |
8 |
{ action: 'cancel', label: 'Cancel', flags: [ 'safe', 'back' ] }, |
9 |
{ action: 'other', label: 'Other', flags: 'other' } |
10 |
]; |
11 |
Demo.ProcessDialog.prototype.initialize = function () { |
12 |
var $content; |
13 |
Demo.ProcessDialog.parent.prototype.initialize.apply( this, arguments ); |
14 |
|
15 |
$content = $( '<div>' ).append( |
Error |
Row 16, Column 3: "Prefer textContent to $.text"
jquery/no-text
|
16 |
$( '<p>' ).text( 'Dialog content' ), |
Error |
Row 17, Column 3: "Prefer textContent to $.text"
jquery/no-text
|
17 |
$( '<a>' ) |
18 |
.text( 'Be alert!' ) |
19 |
.on( 'click', function () { |
20 |
OO.ui.alert( 'You are alert!' ); |
21 |
} ) |
22 |
); |
23 |
|
24 |
this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } ); |
25 |
this.content.$element.append( $content ); |
26 |
this.$body.append( this.content.$element ); |
27 |
}; |
28 |
Demo.ProcessDialog.prototype.getActionProcess = function ( action ) { |
29 |
var dialog = this; |
30 |
if ( action ) { |
31 |
return new OO.ui.Process( function () { |
32 |
dialog.close( { action: action } ); |
33 |
} ); |
34 |
} |
35 |
return Demo.ProcessDialog.parent.prototype.getActionProcess.call( this, action ); |
36 |
}; |
37 |
Demo.ProcessDialog.prototype.getBodyHeight = function () { |
38 |
return this.content.$element.outerHeight( true ); |
39 |
}; |
40 |
|
|
|
/demos/classes/SamplePage.js
|
0 problems
|
|
/demos/classes/SampleTabPanel.js
|
0 problems
|
|
/demos/classes/SearchWidgetDialog.js
|
0 problems
|
|
/demos/classes/SimpleDialog.js
|
0 problems
|
|
/demos/classes/SimpleWidget.js
|
0 problems
|
|
/demos/classes/UnsupportedSelectFileWidget.js
|
0 problems
|
|
/demos/demo.js
|
37 problems (37 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 107, Column 2: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 109, Column 2: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 286, Column 11: "Prefer WeakMap to $.data"
jquery/no-data
|
Error |
Row 304, Column 26: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 455, Column 15: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
Error |
Row 456, Column 3: "Prefer WeakMap to $.data"
jquery/no-data
|
Error |
Row 513, Column 2: "Prefer classList to $.removeClass"
jquery/no-class
|
Error |
Row 514, Column 2: "Prefer classList to $.removeClass"
jquery/no-class
|
Error |
Row 553, Column 9: "Prefer value to $.val"
jquery/no-val
|
Error |
Row 554, Column 3: "Prefer value to $.val"
jquery/no-val
|
Error |
Row 564, Column 4: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 564, Column 4: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 567, Column 4: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 567, Column 4: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 573, Column 17: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 573, Column 17: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 588, Column 3: "Prefer direct property access to $.prop"
jquery/no-prop
|
Error |
Row 588, Column 27: "Prefer direct property access to $.prop"
jquery/no-prop
|
Error |
Row 695, Column 12: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 695, Column 12: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 701, Column 4: "Prefer classList to $.toggleClass"
jquery/no-class
|
Error |
Row 702, Column 9: "Selector extensions are not allowed"
jquery/no-sizzle
|
Error |
Row 702, Column 9: "Prefer matches to $.is"
jquery/no-is
|
Error |
Row 717, Column 7: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 726, Column 9: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 729, Column 11: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 732, Column 11: "Prefer direct property access to $.prop"
jquery/no-prop
|
Error |
Row 732, Column 11: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 736, Column 12: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 736, Column 12: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 746, Column 10: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 748, Column 9: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 752, Column 13: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 789, Column 17: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 789, Column 17: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 789, Column 17: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 798, Column 21: "Prefer getAttribute to $.attr"
jquery/no-attr
|
Line |
Source |
1 |
/* eslint-disable no-console */ |
2 |
/* globals Prism, javascriptStringify */ |
3 |
/** |
4 |
* @class |
5 |
* @extends OO.ui.Element |
6 |
* |
7 |
* @constructor |
8 |
*/ |
9 |
window.Demo = function Demo() { |
10 |
var demo = this; |
11 |
|
12 |
// Parent constructor |
13 |
Demo.parent.call( this ); |
14 |
|
15 |
// Mixin constructors |
16 |
OO.EventEmitter.call( this ); |
17 |
|
18 |
// Normalization |
19 |
this.normalizeQuery(); |
20 |
|
21 |
// Properties |
22 |
this.stylesheetLinks = this.getStylesheetLinks(); |
23 |
this.mode = this.getCurrentMode(); |
24 |
this.$menu = $( '<div>' ); |
25 |
this.pageDropdown = new OO.ui.DropdownWidget( { |
26 |
menu: { |
27 |
items: [ |
28 |
new OO.ui.MenuOptionWidget( { data: 'dialogs', label: 'Dialogs' } ), |
29 |
new OO.ui.MenuOptionWidget( { data: 'icons', label: 'Icons' } ), |
30 |
new OO.ui.MenuOptionWidget( { data: 'toolbars', label: 'Toolbars' } ), |
31 |
new OO.ui.MenuOptionWidget( { data: 'widgets', label: 'Widgets' } ) |
32 |
], |
33 |
// Funny effect... This dropdown is considered to always be "out of viewport" |
34 |
// due to the getViewportSpacing() override below. Don't let it disappear. |
35 |
hideWhenOutOfView: false |
36 |
}, |
37 |
classes: [ 'demo-pageDropdown' ] |
38 |
} ); |
39 |
this.pageMenu = this.pageDropdown.getMenu(); |
40 |
this.themeSelect = new OO.ui.ButtonSelectWidget(); |
41 |
Object.keys( this.constructor.static.themes ).forEach( function ( theme ) { |
42 |
demo.themeSelect.addItems( [ |
43 |
new OO.ui.ButtonOptionWidget( { |
44 |
data: theme, |
45 |
label: demo.constructor.static.themes[ theme ] |
46 |
} ) |
47 |
] ); |
48 |
} ); |
49 |
this.directionSelect = new OO.ui.ButtonSelectWidget().addItems( [ |
50 |
new OO.ui.ButtonOptionWidget( { data: 'ltr', label: 'LTR' } ), |
51 |
new OO.ui.ButtonOptionWidget( { data: 'rtl', label: 'RTL' } ) |
52 |
] ); |
53 |
this.jsPhpSelect = new OO.ui.ButtonGroupWidget().addItems( [ |
54 |
new OO.ui.ButtonWidget( { label: 'JS' } ).setActive( true ), |
55 |
new OO.ui.ButtonWidget( { |
56 |
label: 'PHP', |
57 |
href: 'demos.php' + this.getUrlQuery( this.getCurrentFactorValues() ) |
58 |
} ) |
59 |
] ); |
60 |
this.platformSelect = new OO.ui.ButtonSelectWidget().addItems( [ |
61 |
new OO.ui.ButtonOptionWidget( { data: 'desktop', label: 'Desktop' } ), |
62 |
new OO.ui.ButtonOptionWidget( { data: 'mobile', label: 'Mobile' } ) |
63 |
] ); |
64 |
|
65 |
this.documentationLink = new OO.ui.ButtonWidget( { |
66 |
label: 'Docs', |
67 |
classes: [ 'demo-button-docs' ], |
68 |
icon: 'journal', |
69 |
href: '../js/', |
70 |
flags: [ 'progressive' ] |
71 |
} ); |
72 |
|
73 |
this.tutorialsLink = new OO.ui.ButtonWidget( { |
74 |
label: 'Tutorials', |
75 |
classes: [ 'demo-button-docs' ], |
76 |
icon: 'book', |
77 |
href: 'tutorials/index.html', |
78 |
flags: [ 'progressive' ] |
79 |
} ); |
80 |
|
81 |
// Events |
82 |
this.pageMenu.on( 'choose', OO.ui.bind( this.onModeChange, this ) ); |
83 |
this.themeSelect.on( 'choose', OO.ui.bind( this.onModeChange, this ) ); |
84 |
this.directionSelect.on( 'choose', OO.ui.bind( this.onModeChange, this ) ); |
85 |
this.platformSelect.on( 'choose', OO.ui.bind( this.onModeChange, this ) ); |
86 |
|
87 |
// Initialization |
88 |
this.pageMenu.selectItemByData( this.mode.page ); |
89 |
this.themeSelect.selectItemByData( this.mode.theme ); |
90 |
this.directionSelect.selectItemByData( this.mode.direction ); |
91 |
this.platformSelect.selectItemByData( this.mode.platform ); |
92 |
this.$menu |
93 |
.addClass( 'demo-menu' ) |
94 |
.attr( 'role', 'navigation' ) |
95 |
.append( |
96 |
this.pageDropdown.$element, |
97 |
this.themeSelect.$element, |
98 |
this.directionSelect.$element, |
99 |
this.jsPhpSelect.$element, |
100 |
this.platformSelect.$element, |
101 |
this.documentationLink.$element, |
102 |
this.tutorialsLink.$element |
103 |
); |
104 |
this.$element |
105 |
.addClass( 'demo' ) |
106 |
.append( this.$menu ); |
Error |
Row 107, Column 2: "Prefer setAttribute to $.attr"
jquery/no-attr
|
107 |
$( 'html' ).attr( 'dir', this.mode.direction ); |
108 |
$( 'head' ).append( this.stylesheetLinks ); |
Error |
Row 109, Column 2: "Prefer classList to $.addClass"
jquery/no-class
|
109 |
$( 'body' ).addClass( 'oo-ui-theme-' + this.mode.theme ); |
110 |
// eslint-disable-next-line new-cap |
111 |
OO.ui.theme = new OO.ui[ this.constructor.static.themes[ this.mode.theme ] + 'Theme' ](); |
112 |
OO.ui.isMobile = function () { |
113 |
return demo.mode.platform === 'mobile'; |
114 |
}; |
115 |
OO.ui.getViewportSpacing = function () { |
116 |
return { |
117 |
// Contents of dialogs are shown on top of the fixed menu |
118 |
top: demo.mode.page === 'dialogs' ? 0 : demo.$menu.outerHeight(), |
119 |
right: 0, |
120 |
bottom: 0, |
121 |
left: 0 |
122 |
}; |
123 |
}; |
124 |
}; |
125 |
|
126 |
/* Setup */ |
127 |
|
128 |
OO.inheritClass( Demo, OO.ui.Element ); |
129 |
OO.mixinClass( Demo, OO.EventEmitter ); |
130 |
|
131 |
/* Static Properties */ |
132 |
|
133 |
/** |
134 |
* Available pages. |
135 |
* |
136 |
* Populated by each of the page scripts in the `pages` directory. |
137 |
* |
138 |
* @static |
139 |
* @property {Object.<string,Function>} pages List of functions that render a page, keyed by |
140 |
* symbolic page name |
141 |
*/ |
142 |
Demo.static.pages = {}; |
143 |
|
144 |
/** |
145 |
* Available themes. |
146 |
* |
147 |
* Map of lowercase name to proper name. Lowercase names are used for linking to the |
148 |
* correct stylesheet file. Proper names are used to find the theme class. |
149 |
* |
150 |
* @static |
151 |
* @property {Object.<string,string>} |
152 |
*/ |
153 |
Demo.static.themes = { |
154 |
wikimediaui: 'WikimediaUI', // Do not change this line or you'll break `grunt add-theme` |
155 |
apex: 'Apex' |
156 |
}; |
157 |
|
158 |
/** |
159 |
* Additional suffixes for which each theme defines image modules. |
160 |
* |
161 |
* @static |
162 |
* @property {Object.<string,string[]> |
163 |
*/ |
164 |
Demo.static.additionalThemeImagesSuffixes = { |
165 |
wikimediaui: [ |
166 |
'-icons-movement', |
167 |
'-icons-content', |
168 |
'-icons-alerts', |
169 |
'-icons-interactions', |
170 |
'-icons-moderation', |
171 |
'-icons-editing-core', |
172 |
'-icons-editing-styling', |
173 |
'-icons-editing-list', |
174 |
'-icons-editing-advanced', |
175 |
'-icons-editing-citation', |
176 |
'-icons-media', |
177 |
'-icons-location', |
178 |
'-icons-user', |
179 |
'-icons-layout', |
180 |
'-icons-accessibility', |
181 |
'-icons-wikimedia' |
182 |
], |
183 |
apex: [ |
184 |
'-icons-movement', |
185 |
'-icons-content', |
186 |
'-icons-alerts', |
187 |
'-icons-interactions', |
188 |
'-icons-moderation', |
189 |
'-icons-editing-core', |
190 |
'-icons-editing-styling', |
191 |
'-icons-editing-list', |
192 |
'-icons-editing-advanced', |
193 |
'-icons-editing-citation', |
194 |
'-icons-media', |
195 |
'-icons-location', |
196 |
'-icons-user', |
197 |
'-icons-layout', |
198 |
'-icons-accessibility', |
199 |
'-icons-wikimedia' |
200 |
] |
201 |
}; |
202 |
|
203 |
/** |
204 |
* Available text directions. |
205 |
* |
206 |
* List of text direction descriptions, each containing a `fileSuffix` property used for linking to |
207 |
* the correct stylesheet file. |
208 |
* |
209 |
* @static |
210 |
* @property {Object.<string,Object>} |
211 |
*/ |
212 |
Demo.static.directions = { |
213 |
ltr: { fileSuffix: '' }, |
214 |
rtl: { fileSuffix: '.rtl' } |
215 |
}; |
216 |
|
217 |
/** |
218 |
* Available platforms. |
219 |
* |
220 |
* @static |
221 |
* @property {string[]} |
222 |
*/ |
223 |
Demo.static.platforms = [ 'desktop', 'mobile' ]; |
224 |
|
225 |
/** |
226 |
* Default page. |
227 |
* |
228 |
* @static |
229 |
* @property {string} |
230 |
*/ |
231 |
Demo.static.defaultPage = 'widgets'; |
232 |
|
233 |
/** |
234 |
* Default page. |
235 |
* |
236 |
* Set by one of the page scripts in the `pages` directory. |
237 |
* |
238 |
* @static |
239 |
* @property {string} |
240 |
*/ |
241 |
Demo.static.defaultTheme = 'wikimediaui'; |
242 |
|
243 |
/** |
244 |
* Default page. |
245 |
* |
246 |
* Set by one of the page scripts in the `pages` directory. |
247 |
* |
248 |
* @static |
249 |
* @property {string} |
250 |
*/ |
251 |
Demo.static.defaultDirection = 'ltr'; |
252 |
|
253 |
/** |
254 |
* Default platform. |
255 |
* |
256 |
* Set by one of the page scripts in the `pages` directory. |
257 |
* |
258 |
* @static |
259 |
* @property {string} |
260 |
*/ |
261 |
Demo.static.defaultPlatform = 'desktop'; |
262 |
|
263 |
/* Static Methods */ |
264 |
|
265 |
/** |
266 |
* Scroll to current fragment identifier. We have to do this manually because of the fixed header. |
267 |
*/ |
268 |
Demo.static.scrollToFragment = function () { |
269 |
var elem = document.getElementById( location.hash.slice( 1 ) ); |
270 |
if ( elem ) { |
271 |
// The additional '10' is just because it looks nicer. |
272 |
$( window ).scrollTop( $( elem ).offset().top - $( '.demo-menu' ).outerHeight() - 10 ); |
273 |
} |
274 |
}; |
275 |
|
276 |
/* Methods */ |
277 |
|
278 |
/** |
279 |
* Load the demo page. Must be called after $element is attached. |
280 |
* |
281 |
* @return {jQuery.Promise} Resolved when demo is initialized |
282 |
*/ |
283 |
Demo.prototype.initialize = function () { |
284 |
var demo = this, |
285 |
promises = this.stylesheetLinks.map( function ( el ) { |
Error |
Row 286, Column 11: "Prefer WeakMap to $.data"
jquery/no-data
|
286 |
return $( el ).data( 'load-promise' ); |
287 |
} ); |
288 |
|
289 |
// Helper function to get high resolution profiling data, where available. |
290 |
function now() { |
291 |
return ( window.performance && performance.now ) ? performance.now() : |
292 |
Date.now ? Date.now() : new Date().getTime(); |
293 |
} |
294 |
|
295 |
return $.when.apply( $, promises ) |
296 |
.done( function () { |
297 |
var start, end; |
298 |
start = now(); |
299 |
demo.constructor.static.pages[ demo.mode.page ]( demo ); |
300 |
end = now(); |
301 |
window.console.log( 'Took ' + ( end - start ) + ' ms to build demo page.' ); |
302 |
} ) |
303 |
.fail( function () { |
Error |
Row 304, Column 26: "Prefer textContent to $.text"
jquery/no-text
|
304 |
demo.$element.append( $( '<p>' ).text( 'Demo styles failed to load.' ) ); |
305 |
} ); |
306 |
}; |
307 |
|
308 |
/** |
309 |
* Handle mode change events. |
310 |
* |
311 |
* Will load a new page. |
312 |
*/ |
313 |
Demo.prototype.onModeChange = function () { |
314 |
var page = this.pageMenu.findSelectedItem().getData(), |
315 |
theme = this.themeSelect.findSelectedItem().getData(), |
316 |
direction = this.directionSelect.findSelectedItem().getData(), |
317 |
platform = this.platformSelect.findSelectedItem().getData(); |
318 |
|
319 |
history.pushState( null, document.title, this.getUrlQuery( [ page, theme, direction, platform ] ) ); |
320 |
$( window ).triggerHandler( 'popstate' ); |
321 |
}; |
322 |
|
323 |
/** |
324 |
* Get URL query for given factors describing the demo's mode. |
325 |
* |
326 |
* @param {string[]} factors Factors, as returned e.g. by #getCurrentFactorValues |
327 |
* @return {string} URL query part, starting with '?' |
328 |
*/ |
329 |
Demo.prototype.getUrlQuery = function ( factors ) { |
330 |
return '?page=' + factors[ 0 ] + |
331 |
'&theme=' + factors[ 1 ] + |
332 |
'&direction=' + factors[ 2 ] + |
333 |
'&platform=' + factors[ 3 ] + |
334 |
// Preserve current URL 'fragment' part |
335 |
location.hash; |
336 |
}; |
337 |
|
338 |
/** |
339 |
* Get a list of mode factors. |
340 |
* |
341 |
* Factors are a mapping between symbolic names used in the URL query and internal information used |
342 |
* to act on those symbolic names. |
343 |
* |
344 |
* Factor lists are in URL order: page, theme, direction, platform. Page contains the symbolic |
345 |
* page name, others contain file suffixes. |
346 |
* |
347 |
* @return {Object[]} List of mode factors, keyed by symbolic name |
348 |
*/ |
349 |
Demo.prototype.getFactors = function () { |
350 |
var key, |
351 |
factors = [ {}, {}, {}, {} ]; |
352 |
|
353 |
for ( key in this.constructor.static.pages ) { |
354 |
factors[ 0 ][ key ] = key; |
355 |
} |
356 |
for ( key in this.constructor.static.themes ) { |
357 |
factors[ 1 ][ key ] = '-' + key; |
358 |
} |
359 |
for ( key in this.constructor.static.directions ) { |
360 |
factors[ 2 ][ key ] = this.constructor.static.directions[ key ].fileSuffix; |
361 |
} |
362 |
this.constructor.static.platforms.forEach( function ( platform ) { |
363 |
factors[ 3 ][ platform ] = ''; |
364 |
} ); |
365 |
|
366 |
return factors; |
367 |
}; |
368 |
|
369 |
/** |
370 |
* Get a list of default factors. |
371 |
* |
372 |
* Factor defaults are in URL order: page, theme, direction, platform. Each contains a symbolic |
373 |
* factor name which should be used as a fallback when the URL query is missing or invalid. |
374 |
* |
375 |
* @return {Object[]} List of default factors |
376 |
*/ |
377 |
Demo.prototype.getDefaultFactorValues = function () { |
378 |
return [ |
379 |
this.constructor.static.defaultPage, |
380 |
this.constructor.static.defaultTheme, |
381 |
this.constructor.static.defaultDirection, |
382 |
this.constructor.static.defaultPlatform |
383 |
]; |
384 |
}; |
385 |
|
386 |
/** |
387 |
* Parse the current URL query into factor values. |
388 |
* |
389 |
* @return {string[]} Factor values in URL order: page, theme, direction, platform |
390 |
*/ |
391 |
Demo.prototype.getCurrentFactorValues = function () { |
392 |
var i, parts, index, |
393 |
factors = this.getDefaultFactorValues(), |
394 |
order = [ 'page', 'theme', 'direction', 'platform' ], |
395 |
query = location.search.slice( 1 ).split( '&' ); |
396 |
for ( i = 0; i < query.length; i++ ) { |
397 |
parts = query[ i ].split( '=', 2 ); |
398 |
index = order.indexOf( parts[ 0 ] ); |
399 |
if ( index !== -1 ) { |
400 |
factors[ index ] = decodeURIComponent( parts[ 1 ] ); |
401 |
} |
402 |
} |
403 |
return factors; |
404 |
}; |
405 |
|
406 |
/** |
407 |
* Get the current mode. |
408 |
* |
409 |
* Generated from parsed URL query values. |
410 |
* |
411 |
* @return {Object} List of factor values keyed by factor name |
412 |
*/ |
413 |
Demo.prototype.getCurrentMode = function () { |
414 |
var factorValues = this.getCurrentFactorValues(); |
415 |
|
416 |
return { |
417 |
page: factorValues[ 0 ], |
418 |
theme: factorValues[ 1 ], |
419 |
direction: factorValues[ 2 ], |
420 |
platform: factorValues[ 3 ] |
421 |
}; |
422 |
}; |
423 |
|
424 |
/** |
425 |
* Get link elements for the current mode. |
426 |
* |
427 |
* @return {HTMLElement[]} List of link elements |
428 |
*/ |
429 |
Demo.prototype.getStylesheetLinks = function () { |
430 |
var i, len, links, fragments, |
431 |
factors = this.getFactors(), |
432 |
theme = this.getCurrentFactorValues()[ 1 ], |
433 |
suffixes = this.constructor.static.additionalThemeImagesSuffixes[ theme ] || [], |
434 |
urls = []; |
435 |
|
436 |
// Translate modes to filename fragments |
437 |
fragments = this.getCurrentFactorValues().map( function ( val, index ) { |
438 |
return factors[ index ][ val ]; |
439 |
} ); |
440 |
|
441 |
// Theme styles |
442 |
urls.push( 'dist/oojs-ui' + fragments.slice( 1 ).join( '' ) + '.css' ); |
443 |
for ( i = 0, len = suffixes.length; i < len; i++ ) { |
444 |
urls.push( 'dist/oojs-ui' + fragments[ 1 ] + suffixes[ i ] + fragments[ 2 ] + '.css' ); |
445 |
} |
446 |
|
447 |
// Demo styles |
448 |
urls.push( 'styles/demo' + fragments[ 2 ] + '.css' ); |
449 |
|
450 |
// Add link tags |
451 |
links = urls.map( function ( url ) { |
452 |
var |
453 |
link = document.createElement( 'link' ), |
454 |
$link = $( link ), |
Error |
Row 455, Column 15: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
455 |
deferred = $.Deferred(); |
Error |
Row 456, Column 3: "Prefer WeakMap to $.data"
jquery/no-data
|
456 |
$link.data( 'load-promise', deferred.promise() ); |
457 |
$link.on( { |
458 |
load: deferred.resolve, |
459 |
error: deferred.reject |
460 |
} ); |
461 |
link.rel = 'stylesheet'; |
462 |
link.href = url; |
463 |
return link; |
464 |
} ); |
465 |
|
466 |
return links; |
467 |
}; |
468 |
|
469 |
/** |
470 |
* Normalize the URL query. |
471 |
*/ |
472 |
Demo.prototype.normalizeQuery = function () { |
473 |
var i, len, factorValues, match, valid, factorValue, |
474 |
modes = [], |
475 |
factors = this.getFactors(), |
476 |
defaults = this.getDefaultFactorValues(); |
477 |
|
478 |
factorValues = this.getCurrentFactorValues(); |
479 |
for ( i = 0, len = factors.length; i < len; i++ ) { |
480 |
factorValue = factorValues[ i ]; |
481 |
modes[ i ] = factors[ i ][ factorValue ] !== undefined ? factorValue : defaults[ i ]; |
482 |
} |
483 |
|
484 |
// Backwards-compatibility with old URLs that used the 'fragment' part to link to demo sections: |
485 |
// if a fragment is specified and it describes valid factors, turn the URL into the new style. |
486 |
match = location.hash.match( /^#(\w+)-(\w+)-(\w+)-(\w+)$/ ); |
487 |
if ( match ) { |
488 |
factorValues = []; |
489 |
valid = true; |
490 |
for ( i = 0, len = factors.length; i < len; i++ ) { |
491 |
factorValue = match[ i + 1 ]; |
492 |
if ( factors[ i ][ factorValue ] !== undefined ) { |
493 |
factorValues[ i ] = factorValue; |
494 |
} else { |
495 |
valid = false; |
496 |
break; |
497 |
} |
498 |
} |
499 |
if ( valid ) { |
500 |
location.hash = ''; |
501 |
modes = factorValues; |
502 |
} |
503 |
} |
504 |
|
505 |
// Update query |
506 |
history.replaceState( null, document.title, this.getUrlQuery( modes ) ); |
507 |
}; |
508 |
|
509 |
/** |
510 |
* Destroy demo. |
511 |
*/ |
512 |
Demo.prototype.destroy = function () { |
Error |
Row 513, Column 2: "Prefer classList to $.removeClass"
jquery/no-class
|
513 |
$( 'body' ).removeClass( 'oo-ui-ltr oo-ui-rtl' ); |
Error |
Row 514, Column 2: "Prefer classList to $.removeClass"
jquery/no-class
|
514 |
$( 'body' ).removeClass( 'oo-ui-theme-' + this.mode.theme ); |
515 |
$( this.stylesheetLinks ).remove(); |
516 |
this.$element.remove(); |
517 |
this.emit( 'destroy' ); |
518 |
}; |
519 |
|
520 |
/** |
521 |
* Build a console for interacting with an element. |
522 |
* |
523 |
* @param {OO.ui.Layout} item |
524 |
* @param {string} layout Variable name for layout |
525 |
* @param {string} widget Variable name for layout's field widget |
526 |
* @return {jQuery} Console interface element |
527 |
*/ |
528 |
Demo.prototype.buildConsole = function ( item, layout, widget, showLayoutCode ) { |
529 |
var $toggle, $log, $label, $input, $submit, $console, $form, $pre, $code, |
530 |
console = window.console; |
531 |
|
532 |
function exec( str ) { |
533 |
var func, ret; |
534 |
if ( str.indexOf( 'return' ) !== 0 ) { |
535 |
str = 'return ' + str; |
536 |
} |
537 |
try { |
538 |
// eslint-disable-next-line no-new-func |
539 |
func = new Function( layout, widget, 'item', str ); |
540 |
ret = { value: func( item, item.fieldWidget, item.fieldWidget ) }; |
541 |
} catch ( error ) { |
542 |
ret = { |
543 |
value: undefined, |
544 |
error: error |
545 |
}; |
546 |
} |
547 |
return ret; |
548 |
} |
549 |
|
550 |
function submit() { |
551 |
var val, result, logval; |
552 |
|
Error |
Row 553, Column 9: "Prefer value to $.val"
jquery/no-val
|
553 |
val = $input.val(); |
Error |
Row 554, Column 3: "Prefer value to $.val"
jquery/no-val
|
554 |
$input.val( '' ); |
555 |
$input[ 0 ].focus(); |
556 |
result = exec( val ); |
557 |
|
558 |
logval = String( result.value ); |
559 |
if ( logval === '' ) { |
560 |
logval = '""'; |
561 |
} |
562 |
|
563 |
$log.append( |
Error |
Row 564, Column 4: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 564, Column 4: "Prefer textContent to $.text"
jquery/no-text
|
564 |
$( '<div>' ) |
565 |
.addClass( 'demo-console-log-line demo-console-log-line-input' ) |
566 |
.text( val ), |
Error |
Row 567, Column 4: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 567, Column 4: "Prefer classList to $.addClass"
jquery/no-class
|
567 |
$( '<div>' ) |
568 |
.addClass( 'demo-console-log-line demo-console-log-line-return' ) |
569 |
.text( logval || result.value ) |
570 |
); |
571 |
|
572 |
if ( result.error ) { |
Error |
Row 573, Column 17: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 573, Column 17: "Prefer classList to $.addClass"
jquery/no-class
|
573 |
$log.append( $( '<div>' ).addClass( 'demo-console-log-line demo-console-log-line-error' ).text( result.error ) ); |
574 |
} |
575 |
|
576 |
if ( console && console.log ) { |
577 |
console.log( '[demo]', result.value ); |
578 |
if ( result.error ) { |
579 |
if ( console.error ) { |
580 |
console.error( '[demo]', String( result.error ), result.error ); |
581 |
} else { |
582 |
console.log( '[demo] Error: ', result.error ); |
583 |
} |
584 |
} |
585 |
} |
586 |
|
587 |
// Scrol to end |
Error |
Row 588, Column 3: "Prefer direct property access to $.prop"
jquery/no-prop
|
Error |
Row 588, Column 27: "Prefer direct property access to $.prop"
jquery/no-prop
|
588 |
$log.prop( 'scrollTop', $log.prop( 'scrollHeight' ) ); |
589 |
} |
590 |
|
591 |
function getCode( item, toplevel ) { |
592 |
var config, defaultConfig, url, params, out, i, |
593 |
items = [], |
594 |
demoLinks = [], |
595 |
docLinks = []; |
596 |
|
597 |
function getConstructorName( item ) { |
598 |
var isDemoWidget = item.constructor.name.indexOf( 'Demo' ) === 0; |
599 |
return ( isDemoWidget ? 'Demo.' : 'OO.ui.' ) + item.constructor.name.slice( 4 ); |
600 |
} |
601 |
|
602 |
// If no item was passed we shouldn't show a code block |
603 |
if ( item === undefined ) { |
604 |
return false; |
605 |
} |
606 |
|
607 |
config = item.initialConfig; |
608 |
|
609 |
// Prevent the default config from being part of the code |
610 |
if ( item instanceof OO.ui.ActionFieldLayout ) { |
611 |
defaultConfig = ( new item.constructor( new OO.ui.TextInputWidget(), new OO.ui.ButtonWidget() ) ).initialConfig; |
612 |
} else if ( item instanceof OO.ui.FieldLayout ) { |
613 |
defaultConfig = ( new item.constructor( new OO.ui.ButtonWidget() ) ).initialConfig; |
614 |
} else { |
615 |
defaultConfig = ( new item.constructor() ).initialConfig; |
616 |
} |
617 |
Object.keys( defaultConfig ).forEach( function ( key ) { |
618 |
if ( config[ key ] === defaultConfig[ key ] ) { |
619 |
delete config[ key ]; |
620 |
} else if ( |
621 |
typeof config[ key ] === 'object' && typeof defaultConfig[ key ] === 'object' && |
622 |
OO.compare( config[ key ], defaultConfig[ key ] ) |
623 |
) { |
624 |
delete config[ key ]; |
625 |
} |
626 |
} ); |
627 |
|
628 |
config = javascriptStringify( config, function ( obj, indent, stringify ) { |
629 |
if ( obj instanceof Function ) { |
630 |
// Get function's source code, with extraneous indentation removed |
631 |
return obj.toString().replace( /^\t\t\t\t\t\t/gm, '' ); |
632 |
} else if ( obj instanceof jQuery ) { |
633 |
if ( $.contains( item.$element[ 0 ], obj[ 0 ] ) ) { |
634 |
// If this element appears inside the generated widget, |
635 |
// assume this was something like `$label: $( '<p>Text</p>' )` |
636 |
return '$( ' + javascriptStringify( obj.prop( 'outerHTML' ) ) + ' )'; |
637 |
} else { |
638 |
// Otherwise assume this was something like `$overlay: $( '#overlay' )` |
639 |
return '$( ' + javascriptStringify( '#' + obj.attr( 'id' ) ) + ' )'; |
640 |
} |
641 |
} else if ( obj instanceof OO.ui.HtmlSnippet ) { |
642 |
return 'new OO.ui.HtmlSnippet( ' + javascriptStringify( obj.toString() ) + ' )'; |
643 |
} else if ( obj instanceof OO.ui.Element ) { |
644 |
return getCode( obj ); |
645 |
} else { |
646 |
return stringify( obj ); |
647 |
} |
648 |
}, '\t' ); |
649 |
|
650 |
// The generated code needs to include different arguments, based on the object type |
651 |
items.push( item ); |
652 |
if ( item instanceof OO.ui.ActionFieldLayout ) { |
653 |
params = getCode( item.fieldWidget ) + ', ' + getCode( item.buttonWidget ); |
654 |
items.push( item.fieldWidget ); |
655 |
items.push( item.buttonWidget ); |
656 |
} else if ( item instanceof OO.ui.FieldLayout ) { |
657 |
params = getCode( item.fieldWidget ); |
658 |
items.push( item.fieldWidget ); |
659 |
} else { |
660 |
params = ''; |
661 |
} |
662 |
if ( config !== '{}' ) { |
663 |
params += ( params ? ', ' : '' ) + config; |
664 |
} |
665 |
out = 'new ' + getConstructorName( item ) + '(' + |
666 |
( params ? ' ' : '' ) + params + ( params ? ' ' : '' ) + |
667 |
')'; |
668 |
|
669 |
if ( toplevel ) { |
670 |
for ( i = 0; i < items.length; i++ ) { |
671 |
item = items[ i ]; |
672 |
// The code generated for Demo widgets cannot be copied and used |
673 |
if ( item.constructor.name.indexOf( 'Demo' ) === 0 ) { |
674 |
url = |
675 |
'https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/demos/classes/' + |
676 |
item.constructor.name.slice( 4 ) + '.js'; |
677 |
demoLinks.push( url ); |
678 |
} else { |
679 |
url = 'https://doc.wikimedia.org/oojs-ui/master/js/#!/api/' + getConstructorName( item ); |
680 |
url = '[' + url + '](' + url + ')'; |
681 |
docLinks.push( url ); |
682 |
} |
683 |
} |
684 |
} |
685 |
|
686 |
return ( |
687 |
( docLinks.length ? '// See documentation at: \n// ' : '' ) + |
688 |
docLinks.join( '\n// ' ) + ( docLinks.length ? '\n' : '' ) + |
689 |
( demoLinks.length ? '// See source code:\n// ' : '' ) + |
690 |
demoLinks.join( '\n// ' ) + ( demoLinks.length ? '\n' : '' ) + |
691 |
out |
692 |
); |
693 |
} |
694 |
|
Error |
Row 695, Column 12: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 695, Column 12: "Prefer classList to $.addClass"
jquery/no-class
|
695 |
$toggle = $( '<span>' ) |
696 |
.addClass( 'demo-console-toggle' ) |
697 |
.attr( 'title', 'Toggle console' ) |
698 |
.on( 'click', function ( e ) { |
699 |
var code; |
700 |
e.preventDefault(); |
Error |
Row 701, Column 4: "Prefer classList to $.toggleClass"
jquery/no-class
|
701 |
$console.toggleClass( 'demo-console-collapsed demo-console-expanded' ); |
Error |
Row 702, Column 9: "Selector extensions are not allowed"
jquery/no-sizzle
|
Error |
Row 702, Column 9: "Prefer matches to $.is"
jquery/no-is
|
702 |
if ( $input.is( ':visible' ) ) { |
703 |
$input[ 0 ].focus(); |
704 |
if ( console && console.log ) { |
705 |
window[ layout ] = item; |
706 |
window[ widget ] = item.fieldWidget; |
707 |
console.log( '[demo]', 'Globals ' + layout + ', ' + widget + ' have been set' ); |
708 |
console.log( '[demo]', item ); |
709 |
|
710 |
if ( showLayoutCode === true ) { |
711 |
code = getCode( item, true ); |
712 |
} else { |
713 |
code = getCode( item.fieldWidget, true ); |
714 |
} |
715 |
|
716 |
if ( code ) { |
Error |
Row 717, Column 7: "Prefer textContent to $.text"
jquery/no-text
|
717 |
$code.text( code ); |
718 |
Prism.highlightElement( $code[ 0 ] ); |
719 |
} else { |
720 |
$code.remove(); |
721 |
} |
722 |
} |
723 |
} |
724 |
} ); |
725 |
|
Error |
Row 726, Column 9: "Prefer classList to $.addClass"
jquery/no-class
|
726 |
$log = $( '<div>' ) |
727 |
.addClass( 'demo-console-log' ); |
728 |
|
Error |
Row 729, Column 11: "Prefer classList to $.addClass"
jquery/no-class
|
729 |
$label = $( '<label>' ) |
730 |
.addClass( 'demo-console-label' ); |
731 |
|
Error |
Row 732, Column 11: "Prefer direct property access to $.prop"
jquery/no-prop
|
Error |
Row 732, Column 11: "Prefer classList to $.addClass"
jquery/no-class
|
732 |
$input = $( '<input>' ) |
733 |
.addClass( 'demo-console-input' ) |
734 |
.prop( 'placeholder', '... (predefined: ' + layout + ', ' + widget + ')' ); |
735 |
|
Error |
Row 736, Column 12: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 736, Column 12: "Prefer classList to $.addClass"
jquery/no-class
|
736 |
$submit = $( '<div>' ) |
737 |
.addClass( 'demo-console-submit' ) |
738 |
.text( '↵' ) |
739 |
.on( 'click', submit ); |
740 |
|
741 |
$form = $( '<form>' ).on( 'submit', function ( e ) { |
742 |
e.preventDefault(); |
743 |
submit(); |
744 |
} ); |
745 |
|
Error |
Row 746, Column 10: "Prefer classList to $.addClass"
jquery/no-class
|
746 |
$code = $( '<code>' ).addClass( 'language-javascript' ); |
747 |
|
Error |
Row 748, Column 9: "Prefer classList to $.addClass"
jquery/no-class
|
748 |
$pre = $( '<pre>' ) |
749 |
.addClass( 'demo-sample-code' ) |
750 |
.append( $code ); |
751 |
|
Error |
Row 752, Column 13: "Prefer classList to $.addClass"
jquery/no-class
|
752 |
$console = $( '<div>' ) |
753 |
.addClass( 'demo-console demo-console-collapsed' ) |
754 |
.append( |
755 |
$toggle, |
756 |
$log, |
757 |
$form.append( |
758 |
$label.append( |
759 |
$input |
760 |
), |
761 |
$submit |
762 |
), |
763 |
$pre |
764 |
); |
765 |
|
766 |
return $console; |
767 |
}; |
768 |
|
769 |
/** |
770 |
* Build a link to this example. |
771 |
* |
772 |
* @param {OO.ui.Layout} item |
773 |
* @param {OO.ui.FieldsetLayout} parentItem |
774 |
* @return {jQuery} Link interface element |
775 |
*/ |
776 |
Demo.prototype.buildLinkExample = function ( item, parentItem ) { |
777 |
var $linkExample, label, fragment; |
778 |
|
779 |
if ( item.$label.text() === '' ) { |
780 |
item = parentItem; |
781 |
} |
782 |
fragment = item.elementId; |
783 |
if ( !fragment ) { |
784 |
label = item.$label.text(); |
785 |
fragment = label.replace( /[^\w]+/g, '-' ).replace( /^-|-$/g, '' ); |
786 |
item.setElementId( fragment ); |
787 |
} |
788 |
|
Error |
Row 789, Column 17: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 789, Column 17: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 789, Column 17: "Prefer classList to $.addClass"
jquery/no-class
|
789 |
$linkExample = $( '<a>' ) |
790 |
.addClass( 'demo-link-example' ) |
791 |
.attr( 'title', 'Link to this example' ) |
792 |
.attr( 'href', '#' + fragment ) |
793 |
.on( 'click', function ( e ) { |
794 |
// We have to handle this manually in order to call .scrollToFragment() even if it's the same |
795 |
// fragment. Normally, the browser will scroll but not fire a 'hashchange' event in this |
796 |
// situation, and the scroll position will be off because of our fixed header. |
797 |
if ( e.which === OO.ui.MouseButtons.LEFT ) { |
Error |
Row 798, Column 21: "Prefer getAttribute to $.attr"
jquery/no-attr
|
798 |
location.hash = $( this ).attr( 'href' ); |
799 |
Demo.static.scrollToFragment(); |
800 |
e.preventDefault(); |
801 |
} |
802 |
} ); |
803 |
|
804 |
return $linkExample; |
805 |
}; |
806 |
|
|
|
/demos/infusion.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 19, Column 2: "Prefer Array#map to $.map"
jquery/no-map
|
Line |
Source |
1 |
// Demonstrate JavaScript 'infusion' of PHP-generated widgets. |
2 |
// Used by widgets.php. |
3 |
|
4 |
var infuseButton, $demoMenu; |
5 |
|
6 |
// Helper function to get high resolution profiling data, where available. |
7 |
function now() { |
8 |
return ( window.performance && performance.now ) ? performance.now() : |
9 |
Date.now ? Date.now() : new Date().getTime(); |
10 |
} |
11 |
|
12 |
// Add a button to infuse everything! |
13 |
// (You wouldn't typically do this: you'd only infuse those objects which you needed to attach |
14 |
// client-side behaviors to, or where the JS implementation provides additional features over PHP, |
15 |
// like DropdownInputWidget. We do it here because it's a good overall test.) |
16 |
function infuseAll() { |
17 |
var start, end; |
18 |
start = now(); |
Error |
Row 19, Column 2: "Prefer Array#map to $.map"
jquery/no-map
|
19 |
$( '*[data-ooui]' ).map( function ( _, e ) { |
20 |
return OO.ui.infuse( e.id ); |
21 |
} ); |
22 |
end = now(); |
23 |
window.console.log( 'Took ' + ( end - start ) + ' ms to infuse demo page.' ); |
24 |
infuseButton.setDisabled( true ); |
25 |
} |
26 |
|
27 |
$demoMenu = $( '.demo-menu' ); |
28 |
|
29 |
OO.ui.getViewportSpacing = function () { |
30 |
return { |
31 |
top: $demoMenu.outerHeight(), |
32 |
right: 0, |
33 |
bottom: 0, |
34 |
left: 0 |
35 |
}; |
36 |
}; |
37 |
|
38 |
// More typical usage: we take the existing server-side |
39 |
// button group and do things to it, here adding a new button. |
40 |
infuseButton = new OO.ui.ButtonWidget( { label: 'Infuse' } ) |
41 |
.on( 'click', infuseAll ); |
42 |
|
43 |
OO.ui.ButtonGroupWidget.static.infuse( 'demo-menu-infuse' ) |
44 |
.addItems( [ infuseButton ] ); |
45 |
|
|
|
/demos/pages/dialogs.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 292, Column 12: "Prefer textContent to $.text"
jquery/no-text
|
Line |
Source |
1 |
Demo.static.pages.dialogs = function ( demo ) { |
2 |
var i, j, name, openButton, fieldset, examples, DialogClass, config, |
3 |
$demo = demo.$element, |
4 |
$fieldsets = $( [] ), |
5 |
windows = {}, |
6 |
windowManager = new OO.ui.WindowManager(); |
7 |
|
8 |
config = [ |
9 |
{ |
10 |
name: 'Convenience functions', |
11 |
examples: [ |
12 |
{ |
13 |
name: 'Quick alert', |
14 |
method: 'alert', |
15 |
param: 'Alert message.' |
16 |
}, |
17 |
{ |
18 |
name: 'Larger alert', |
19 |
method: 'alert', |
20 |
param: 'Alert message.', |
21 |
data: { size: 'larger' } |
22 |
}, |
23 |
{ |
24 |
name: 'Quick confirm', |
25 |
method: 'confirm', |
26 |
param: 'Confirmation message?' |
27 |
}, |
28 |
{ |
29 |
name: 'Quick prompt', |
30 |
method: 'prompt', |
31 |
param: 'Text prompt:' |
32 |
} |
33 |
] |
34 |
}, |
35 |
{ |
36 |
name: 'Dialog interface', |
37 |
examples: [ |
38 |
{ |
39 |
name: 'Simple dialog (small)', |
40 |
config: { |
41 |
size: 'small' |
42 |
} |
43 |
}, |
44 |
{ |
45 |
name: 'Simple dialog (medium)', |
46 |
config: { |
47 |
size: 'medium' |
48 |
} |
49 |
}, |
50 |
{ |
51 |
name: 'Simple dialog (large)', |
52 |
config: { |
53 |
size: 'large' |
54 |
} |
55 |
}, |
56 |
{ |
57 |
name: 'Simple dialog (larger)', |
58 |
config: { |
59 |
size: 'larger' |
60 |
} |
61 |
}, |
62 |
{ |
63 |
name: 'Simple dialog (full)', |
64 |
config: { |
65 |
size: 'full' |
66 |
} |
67 |
}, |
68 |
{ |
69 |
name: 'Simple dialog (delayed ready process)', |
70 |
dialogClass: Demo.DelayedReadyProcessDialog, |
71 |
config: { |
72 |
size: 'large' |
73 |
} |
74 |
}, |
75 |
{ |
76 |
name: 'Simple dialog (failed ready process)', |
77 |
dialogClass: Demo.FailedReadyProcessDialog, |
78 |
config: { |
79 |
size: 'large' |
80 |
} |
81 |
}, |
82 |
{ |
83 |
name: 'Simple dialog (failed setup process)', |
84 |
dialogClass: Demo.FailedSetupProcessDialog, |
85 |
config: { |
86 |
size: 'large' |
87 |
} |
88 |
}, |
89 |
{ |
90 |
name: 'Process dialog (medium)', |
91 |
dialogClass: Demo.ProcessDialog, |
92 |
config: { |
93 |
size: 'medium' |
94 |
} |
95 |
}, |
96 |
{ |
97 |
name: 'Process dialog (medium, long title)', |
98 |
dialogClass: Demo.ProcessDialog, |
99 |
config: { |
100 |
size: 'medium' |
101 |
}, |
102 |
data: { |
103 |
title: 'Sample dialog with very long title that does not remotely fit into the space available and thus demonstrates what happens in that use case' |
104 |
} |
105 |
}, |
106 |
{ |
107 |
name: 'Process dialog (medium, long)', |
108 |
dialogClass: Demo.LongProcessDialog, |
109 |
config: { |
110 |
size: 'medium' |
111 |
} |
112 |
}, |
113 |
{ |
114 |
name: 'Process dialog (full)', |
115 |
dialogClass: Demo.ProcessDialog, |
116 |
config: { |
117 |
size: 'full' |
118 |
} |
119 |
}, |
120 |
{ |
121 |
name: 'Broken dialog (error handling)', |
122 |
dialogClass: Demo.BrokenDialog, |
123 |
config: { |
124 |
size: 'medium' |
125 |
} |
126 |
}, |
127 |
{ |
128 |
name: 'Message dialog (generic)', |
129 |
dialogClass: OO.ui.MessageDialog, |
130 |
data: { |
131 |
title: 'Continue?', |
132 |
message: 'It may be risky' |
133 |
} |
134 |
}, |
135 |
{ |
136 |
name: 'Message dialog (lengthy)', |
137 |
dialogClass: OO.ui.MessageDialog, |
138 |
data: { |
139 |
title: 'Continue?', |
140 |
message: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque quis laoreet elit. Nam eu velit ullamcorper, volutpat elit sed, viverra massa. Aenean congue aliquam lorem, et laoreet risus condimentum vel. Praesent nec imperdiet mauris. Nunc eros magna, iaculis sit amet ante id, dapibus tristique lorem. Praesent in feugiat lorem, sit amet porttitor eros. Donec sapien turpis, pretium eget ligula id, scelerisque tincidunt diam. Pellentesque a venenatis tortor, at luctus nisl. Quisque vel urna a enim mattis rutrum. Morbi eget consequat nisl. Nam tristique molestie diam ac consequat. Nam varius adipiscing mattis. Praesent sodales volutpat nulla lobortis iaculis. Quisque vel odio eget diam posuere imperdiet. Fusce et iaculis odio. Donec in nibh ut dui accumsan vehicula quis et massa.' |
141 |
} |
142 |
}, |
143 |
{ |
144 |
name: 'Message dialog (1 action)', |
145 |
dialogClass: OO.ui.MessageDialog, |
146 |
data: { |
147 |
title: 'Storage limit reached', |
148 |
message: 'You are out of disk space', |
149 |
actions: [ |
150 |
{ |
151 |
action: 'accept', |
152 |
label: 'Dismiss', |
153 |
flags: 'primary' |
154 |
} |
155 |
] |
156 |
} |
157 |
}, |
158 |
{ |
159 |
name: 'Message dialog (2 actions)', |
160 |
dialogClass: OO.ui.MessageDialog, |
161 |
data: { |
162 |
title: 'Cannot save data', |
163 |
message: 'The server is not responding', |
164 |
actions: [ |
165 |
{ |
166 |
action: 'reject', |
167 |
label: 'Cancel', |
168 |
flags: [ 'safe', 'back' ] |
169 |
}, |
170 |
{ |
171 |
action: 'repeat', |
172 |
label: 'Try again', |
173 |
flags: [ 'primary', 'progressive' ] |
174 |
} |
175 |
] |
176 |
} |
177 |
}, |
178 |
{ |
179 |
name: 'Message dialog (3 actions)', |
180 |
dialogClass: OO.ui.MessageDialog, |
181 |
data: { |
182 |
title: 'Delete file?', |
183 |
message: 'The file will be irreversably obliterated. Proceed with caution.', |
184 |
actions: [ |
185 |
{ action: 'reject', label: 'Cancel', flags: [ 'safe', 'back' ] }, |
186 |
{ action: 'reject', label: 'Move file to trash' }, |
187 |
{ |
188 |
action: 'accept', |
189 |
label: 'Obliterate', |
190 |
flags: [ 'primary', 'destructive' ] |
191 |
} |
192 |
] |
193 |
} |
194 |
} |
195 |
] |
196 |
}, |
197 |
{ |
198 |
name: 'Elements best used inside dialogs', |
199 |
examples: [ |
200 |
{ |
201 |
name: 'Search widget dialog (medium)', |
202 |
dialogClass: Demo.SearchWidgetDialog, |
203 |
config: { |
204 |
size: 'medium' |
205 |
} |
206 |
}, |
207 |
{ |
208 |
name: 'Booklet dialog', |
209 |
dialogClass: Demo.BookletDialog, |
210 |
config: { |
211 |
size: 'medium' |
212 |
} |
213 |
}, |
214 |
{ |
215 |
name: 'Outlined booklet dialog (aside navigation)', |
216 |
dialogClass: Demo.OutlinedBookletDialog, |
217 |
config: { |
218 |
size: 'medium' |
219 |
} |
220 |
}, |
221 |
{ |
222 |
name: 'Continuous outlined booklet dialog (aside navigation)', |
223 |
dialogClass: Demo.ContinuousOutlinedBookletDialog, |
224 |
config: { |
225 |
size: 'medium' |
226 |
} |
227 |
}, |
228 |
{ |
229 |
name: 'Indexed dialog (tab navigation)', |
230 |
dialogClass: Demo.IndexedDialog, |
231 |
config: { |
232 |
size: 'medium' |
233 |
} |
234 |
}, |
235 |
{ |
236 |
name: 'Menu dialog', |
237 |
dialogClass: Demo.MenuDialog, |
238 |
config: { |
239 |
size: 'medium' |
240 |
} |
241 |
} |
242 |
] |
243 |
}, |
244 |
{ |
245 |
name: 'Low-level test cases', |
246 |
examples: [ |
247 |
{ |
248 |
name: 'FloatableElement test', |
249 |
dialogClass: Demo.FloatableTest, |
250 |
config: { |
251 |
size: 'medium' |
252 |
} |
253 |
}, |
254 |
{ |
255 |
name: 'Dialog with dropdowns ($overlay test)', |
256 |
dialogClass: Demo.DialogWithDropdowns, |
257 |
config: { |
258 |
size: 'large' |
259 |
} |
260 |
}, |
261 |
{ |
262 |
name: 'PopupButtonWidget test', |
263 |
dialogClass: Demo.PopupButtonWidgetTest, |
264 |
config: { |
265 |
size: 'large' |
266 |
} |
267 |
}, |
268 |
{ |
269 |
name: 'Dialog with popup and dropdown (ClippableElement test)', |
270 |
dialogClass: Demo.DialogWithPopupAndDropdown, |
271 |
config: { |
272 |
size: 'large' |
273 |
} |
274 |
} |
275 |
] |
276 |
} |
277 |
]; |
278 |
|
279 |
function openDialog( name, data ) { |
280 |
windowManager.openWindow( name, data ); |
281 |
} |
282 |
|
283 |
for ( j = 0; j < config.length; j++ ) { |
284 |
fieldset = new OO.ui.FieldsetLayout( { label: config[ j ].name } ); |
285 |
examples = config[ j ].examples; |
286 |
$fieldsets = $fieldsets.add( fieldset.$element ); |
287 |
|
288 |
for ( i = 0; i < examples.length; i++ ) { |
289 |
openButton = new OO.ui.ButtonWidget( { |
290 |
framed: false, |
291 |
icon: 'window', |
Error |
Row 292, Column 12: "Prefer textContent to $.text"
jquery/no-text
|
292 |
label: $( '<span dir="ltr"></span>' ).text( examples[ i ].name ) |
293 |
} ); |
294 |
|
295 |
if ( examples[ i ].method ) { |
296 |
openButton.on( |
297 |
'click', OO.ui.bind( |
298 |
OO.ui, |
299 |
examples[ i ].method, |
300 |
examples[ i ].param, |
301 |
examples[ i ].data |
302 |
) |
303 |
); |
304 |
} else { |
305 |
name = 'window_' + j + '_' + i; |
306 |
DialogClass = examples[ i ].dialogClass || Demo.SimpleDialog; |
307 |
windows[ name ] = new DialogClass( examples[ i ].config ); |
308 |
openButton.on( |
309 |
'click', OO.ui.bind( openDialog, this, name, examples[ i ].data ) |
310 |
); |
311 |
} |
312 |
|
313 |
fieldset.addItems( [ new OO.ui.FieldLayout( openButton, { align: 'inline' } ) ] ); |
314 |
} |
315 |
} |
316 |
windowManager.addWindows( windows ); |
317 |
|
318 |
$demo.append( |
319 |
new OO.ui.PanelLayout( { |
320 |
expanded: false, |
321 |
framed: true |
322 |
} ).$element |
323 |
.addClass( 'demo-container' ) |
324 |
.attr( 'role', 'main' ) |
325 |
.append( $fieldsets ), |
326 |
windowManager.$element |
327 |
); |
328 |
|
329 |
demo.once( 'destroy', function () { |
330 |
windowManager.destroy(); |
331 |
OO.ui.getWindowManager().closeWindow( OO.ui.getWindowManager().getCurrentWindow() ); |
332 |
} ); |
333 |
}; |
334 |
|
|
|
/demos/pages/icons.js
|
0 problems
|
|
/demos/pages/toolbars.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 69, Column 3: "Prefer Array#forEach to $.each"
jquery/no-each
|
Line |
Source |
1 |
Demo.static.pages.toolbars = function ( demo ) { |
2 |
var i, toolGroups, actionButton, actionButtonDelete, actionButtonDisabled, actionGroup, publishButton, AlertTool, PopupTool, ToolGroupTool, |
3 |
setDisabled = function () { this.setDisabled( true ); }, |
4 |
$demo = demo.$element, |
5 |
$containers = $(), |
6 |
toolFactories = [], |
7 |
toolGroupFactories = [], |
8 |
toolbars = [], |
9 |
configs = [ |
10 |
{ $overlay: true }, |
11 |
{ $overlay: true, actions: true }, |
12 |
{}, |
13 |
{ actions: true }, |
14 |
{ position: 'bottom' }, |
15 |
{ actions: true, position: 'bottom' }, |
16 |
{ $overlay: true }, |
17 |
{ $overlay: true, actions: true } |
18 |
]; |
19 |
|
20 |
// Show some random accelerator keys that don't actually work |
21 |
function getToolAccelerator( name ) { |
22 |
return { |
23 |
listTool1: 'Ctrl+Shift+1', |
24 |
listTool2: 'Ctrl+Alt+2', |
25 |
listTool3: 'Cmd+Enter', |
26 |
listTool5: 'Shift+Down', |
27 |
menuTool: 'Ctrl+M' |
28 |
}[ name ]; |
29 |
} |
30 |
|
31 |
for ( i = 0; i <= 7; i++ ) { |
32 |
toolFactories.push( new OO.ui.ToolFactory() ); |
33 |
toolGroupFactories.push( new OO.ui.ToolGroupFactory() ); |
34 |
toolbars.push( new OO.ui.Toolbar( toolFactories[ i ], toolGroupFactories[ i ], configs[ i ] ) ); |
35 |
toolbars[ i ].getToolAccelerator = getToolAccelerator; |
36 |
} |
37 |
|
38 |
function createTool( toolbar, group, name, icon, title, init, onSelect, displayBothIconAndLabel ) { |
39 |
var Tool = function () { |
40 |
Tool.parent.apply( this, arguments ); |
41 |
this.toggled = false; |
42 |
if ( init ) { |
43 |
init.call( this ); |
44 |
} |
45 |
}; |
46 |
|
47 |
OO.inheritClass( Tool, OO.ui.Tool ); |
48 |
|
49 |
Tool.prototype.onSelect = function () { |
50 |
if ( onSelect ) { |
51 |
onSelect.call( this ); |
52 |
} else { |
53 |
this.toggled = !this.toggled; |
54 |
this.setActive( this.toggled ); |
55 |
} |
56 |
toolbars[ toolbar ].emit( 'updateState' ); |
57 |
}; |
58 |
Tool.prototype.onUpdateState = function () {}; |
59 |
|
60 |
Tool.static.name = name; |
61 |
Tool.static.group = group; |
62 |
Tool.static.icon = icon; |
63 |
Tool.static.title = title; |
64 |
Tool.static.displayBothIconAndLabel = !!displayBothIconAndLabel; |
65 |
return Tool; |
66 |
} |
67 |
|
68 |
function createToolGroup( toolbar, group ) { |
Error |
Row 69, Column 3: "Prefer Array#forEach to $.each"
jquery/no-each
|
69 |
$.each( toolGroups[ group ], function ( i, tool ) { |
70 |
var args = tool.slice(); |
71 |
args.splice( 0, 0, toolbar, group ); |
72 |
toolFactories[ toolbar ].register( createTool.apply( null, args ) ); |
73 |
} ); |
74 |
} |
75 |
|
76 |
function createDisabledToolGroup( parent, name ) { |
77 |
var DisabledToolGroup = function () { |
78 |
DisabledToolGroup.parent.apply( this, arguments ); |
79 |
this.setDisabled( true ); |
80 |
}; |
81 |
|
82 |
OO.inheritClass( DisabledToolGroup, parent ); |
83 |
|
84 |
DisabledToolGroup.static.name = name; |
85 |
|
86 |
DisabledToolGroup.prototype.onUpdateState = function () { |
87 |
this.setLabel( 'Disabled' ); |
88 |
}; |
89 |
|
90 |
return DisabledToolGroup; |
91 |
} |
92 |
|
93 |
toolGroupFactories[ 0 ].register( createDisabledToolGroup( OO.ui.BarToolGroup, 'disabledBar' ) ); |
94 |
toolGroupFactories[ 0 ].register( createDisabledToolGroup( OO.ui.ListToolGroup, 'disabledList' ) ); |
95 |
toolGroupFactories[ 1 ].register( createDisabledToolGroup( OO.ui.MenuToolGroup, 'disabledMenu' ) ); |
96 |
|
97 |
AlertTool = function ( toolGroup, config ) { |
98 |
// Parent constructor |
99 |
OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: { |
100 |
padded: true, |
101 |
label: 'Alert head', |
102 |
head: true |
103 |
} }, config ) ); |
104 |
|
105 |
this.popup.$body.append( '<p>Alert contents</p>' ); |
106 |
}; |
107 |
|
108 |
OO.inheritClass( AlertTool, OO.ui.PopupTool ); |
109 |
|
110 |
AlertTool.static.name = 'alertTool'; |
111 |
AlertTool.static.group = 'popupTools'; |
112 |
AlertTool.static.icon = 'alert'; |
113 |
|
114 |
toolFactories[ 2 ].register( AlertTool ); |
115 |
toolFactories[ 4 ].register( AlertTool ); |
116 |
|
117 |
PopupTool = function ( toolGroup, config ) { |
118 |
// Parent constructor |
119 |
OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: { |
120 |
padded: true, |
121 |
label: 'Popup head', |
122 |
head: true |
123 |
} }, config ) ); |
124 |
|
125 |
this.popup.$body.append( '<p>Popup contents</p>' ); |
126 |
}; |
127 |
|
128 |
OO.inheritClass( PopupTool, OO.ui.PopupTool ); |
129 |
|
130 |
PopupTool.static.name = 'popupTool'; |
131 |
PopupTool.static.group = 'popupTools'; |
132 |
PopupTool.static.icon = 'help'; |
133 |
|
134 |
toolFactories[ 2 ].register( PopupTool ); |
135 |
toolFactories[ 4 ].register( PopupTool ); |
136 |
|
137 |
ToolGroupTool = function ( toolGroup, config ) { |
138 |
// Parent constructor |
139 |
OO.ui.ToolGroupTool.call( this, toolGroup, config ); |
140 |
}; |
141 |
|
142 |
OO.inheritClass( ToolGroupTool, OO.ui.ToolGroupTool ); |
143 |
|
144 |
ToolGroupTool.static.name = 'toolGroupTool'; |
145 |
ToolGroupTool.static.group = 'barTools'; |
146 |
ToolGroupTool.static.groupConfig = { |
147 |
label: 'More', |
148 |
include: [ { group: 'moreListTools' } ] |
149 |
}; |
150 |
|
151 |
toolFactories[ 0 ].register( ToolGroupTool ); |
152 |
toolFactories[ 3 ].register( ToolGroupTool ); |
153 |
toolFactories[ 5 ].register( ToolGroupTool ); |
154 |
|
155 |
// Toolbars setup, in order of toolbar items appearance |
156 |
// Toolbar |
157 |
toolbars[ 0 ].setup( [ |
158 |
{ |
159 |
name: 'bar', |
160 |
type: 'bar', |
161 |
include: [ { group: 'barTools' } ], |
162 |
demote: [ 'toolGroupTool' ] |
163 |
}, |
164 |
{ |
165 |
name: 'disabledBar', |
166 |
type: 'disabledBar', |
167 |
include: [ { group: 'disabledBarTools' } ] |
168 |
}, |
169 |
{ |
170 |
name: 'list', |
171 |
type: 'list', |
172 |
label: 'List', |
173 |
icon: 'image', |
174 |
include: [ { group: 'listTools' } ], |
175 |
allowCollapse: [ 'listTool1', 'listTool6' ] |
176 |
}, |
177 |
{ |
178 |
name: 'disabledList', |
179 |
type: 'disabledList', |
180 |
label: 'List', |
181 |
icon: 'image', |
182 |
include: [ { group: 'disabledListTools' } ] |
183 |
}, |
184 |
{ |
185 |
name: 'autoDisabledList', |
186 |
type: 'list', |
187 |
label: 'Auto-disabling list', |
188 |
icon: 'image', |
189 |
include: [ { group: 'autoDisableListTools' } ] |
190 |
}, |
191 |
{ |
192 |
name: 'catchAll', |
193 |
label: 'Catch-all', |
194 |
include: '*' |
195 |
} |
196 |
] ); |
197 |
// Toolbar with action buttons |
198 |
toolbars[ 1 ].setup( [ |
199 |
{ |
200 |
name: 'menu', |
201 |
type: 'menu', |
202 |
header: 'Popup-/MenuToolGroup header', |
203 |
icon: 'image', |
204 |
include: [ { group: 'menuTools' } ] |
205 |
}, |
206 |
{ |
207 |
name: 'disabledMenu', |
208 |
type: 'disabledMenu', |
209 |
icon: 'image', |
210 |
include: [ { group: 'disabledMenuTools' } ] |
211 |
}, |
212 |
{ |
213 |
name: 'cite', |
214 |
type: 'bar', |
215 |
include: [ { group: 'cite' } ] |
216 |
}, |
217 |
{ |
218 |
name: 'citeDisabled', |
219 |
type: 'bar', |
220 |
include: [ { group: 'citeDisabled' } ] |
221 |
} |
222 |
] ); |
223 |
// Action toolbar for toolbars[ 3 ] below |
224 |
toolbars[ 2 ].setup( [ |
225 |
{ |
226 |
name: 'popup', |
227 |
include: [ { group: 'popupTools' } ] |
228 |
}, |
229 |
{ |
230 |
name: 'overflow', |
231 |
type: 'list', |
232 |
icon: 'menu', |
233 |
indicator: '', |
234 |
include: [ { group: 'overflowTools' } ] |
235 |
}, |
236 |
{ |
237 |
name: 'editorSwitch', |
238 |
type: 'list', |
239 |
icon: 'edit', |
240 |
include: [ { group: 'editorSwitchTools' } ] |
241 |
} |
242 |
] ); |
243 |
// Word processor toolbar |
244 |
toolbars[ 3 ].setup( [ |
245 |
{ |
246 |
name: 'history', |
247 |
type: 'bar', |
248 |
include: [ { group: 'history' } ] |
249 |
}, |
250 |
{ |
251 |
name: 'format', |
252 |
type: 'menu', |
253 |
include: [ { group: 'formatTools' } ] |
254 |
}, |
255 |
{ |
256 |
name: 'textStyle', |
257 |
type: 'list', |
258 |
icon: 'textStyle', |
259 |
include: [ { group: 'styleTools' } ] |
260 |
}, |
261 |
{ |
262 |
name: 'link', |
263 |
type: 'bar', |
264 |
include: [ { group: 'link' } ] |
265 |
}, |
266 |
{ |
267 |
name: 'cite', |
268 |
type: 'bar', |
269 |
include: [ { group: 'cite' } ] |
270 |
}, |
271 |
{ |
272 |
name: 'citeDisabled', |
273 |
type: 'bar', |
274 |
include: [ { group: 'citeDisabled' } ] |
275 |
}, |
276 |
{ |
277 |
name: 'structure', |
278 |
type: 'list', |
279 |
icon: 'listBullet', |
280 |
include: [ { group: 'structureTools' } ] |
281 |
}, |
282 |
{ |
283 |
name: 'insert', |
284 |
type: 'list', |
285 |
label: 'Insert', |
286 |
include: [ { group: 'insertTools' }, { group: 'autoDisableListTools' }, { group: 'unusedStuff' } ], |
287 |
allowCollapse: [ 'comment', 'hieroglyphs', 'score', 'signature', 'gallery', 'chem', 'math', 'syntaxHighlightDialog', 'graph', 'referencesList' ] |
288 |
}, |
289 |
{ |
290 |
name: 'specialCharacters', |
291 |
type: 'bar', |
292 |
include: [ { group: 'specialCharacters' } ] |
293 |
} |
294 |
] ); |
295 |
// Action toolbar for toolbars[ 5 ] below |
296 |
toolbars[ 4 ].setup( [ |
297 |
{ |
298 |
name: 'popup', |
299 |
include: [ { group: 'popupTools' } ] |
300 |
}, |
301 |
{ |
302 |
name: 'alert', |
303 |
include: [ { group: 'alertTools' } ] |
304 |
}, |
305 |
{ |
306 |
name: 'overflow', |
307 |
type: 'list', |
308 |
icon: 'menu', |
309 |
indicator: '', |
310 |
include: [ { group: 'overflowTools' } ] |
311 |
}, |
312 |
{ |
313 |
name: 'editorSwitch', |
314 |
type: 'list', |
315 |
icon: 'edit', |
316 |
include: [ { group: 'editorSwitchTools' } ] |
317 |
} |
318 |
] ); |
319 |
// Word processor toolbar set to `position: 'bottom'` |
320 |
toolbars[ 5 ].setup( [ |
321 |
{ |
322 |
name: 'history', |
323 |
type: 'bar', |
324 |
include: [ { group: 'history' } ] |
325 |
}, |
326 |
{ |
327 |
name: 'format', |
328 |
type: 'menu', |
329 |
include: [ { group: 'formatTools' } ] |
330 |
}, |
331 |
{ |
332 |
name: 'style', |
333 |
type: 'list', |
334 |
icon: 'textStyle', |
335 |
include: [ { group: 'styleTools' } ] |
336 |
}, |
337 |
{ |
338 |
name: 'link', |
339 |
type: 'bar', |
340 |
include: [ { group: 'link' } ] |
341 |
}, |
342 |
{ |
343 |
name: 'cite', |
344 |
type: 'bar', |
345 |
include: [ { group: 'cite' } ] |
346 |
}, |
347 |
{ |
348 |
name: 'citeDisabled', |
349 |
type: 'bar', |
350 |
include: [ { group: 'citeDisabled' } ] |
351 |
}, |
352 |
{ |
353 |
name: 'structure', |
354 |
type: 'list', |
355 |
icon: 'listBullet', |
356 |
include: [ { group: 'structureTools' } ] |
357 |
}, |
358 |
{ |
359 |
name: 'insert', |
360 |
type: 'list', |
361 |
label: 'Insert', |
362 |
include: [ { group: 'insertTools' }, { group: 'autoDisableListTools' }, { group: 'unusedStuff' } ] |
363 |
}, |
364 |
{ |
365 |
name: 'specialCharacters', |
366 |
type: 'bar', |
367 |
include: [ { group: 'specialCharacters' } ] |
368 |
} |
369 |
] ); |
370 |
// Action toolbar for toolbars[7] |
371 |
toolbars[ 6 ].setup( [ |
372 |
{ |
373 |
name: 'list', |
374 |
type: 'list', |
375 |
indicator: 'down', |
376 |
flags: [ 'primary', 'progressive' ], |
377 |
include: [ { group: 'listTools' } ] |
378 |
} |
379 |
] ); |
380 |
// Toolbar with action buttons, in a ButtonGroup |
381 |
toolbars[ 7 ].setup( [ |
382 |
{ |
383 |
name: 'menu', |
384 |
type: 'menu', |
385 |
icon: 'image', |
386 |
include: [ { group: 'menuTools' } ] |
387 |
}, |
388 |
{ |
389 |
name: 'disabledMenu', |
390 |
type: 'disabledMenu', |
391 |
icon: 'image', |
392 |
include: [ { group: 'disabledMenuTools' } ] |
393 |
} |
394 |
] ); |
395 |
|
396 |
actionButton = new OO.ui.ButtonWidget( { label: 'Action' } ); |
397 |
actionButtonDisabled = new OO.ui.ButtonWidget( { label: 'Disabled', disabled: true } ); |
398 |
toolbars[ 1 ].$actions.append( actionButton.$element, actionButtonDisabled.$element ); |
399 |
|
400 |
for ( i = 3; i <= 5; i += 2 ) { |
401 |
publishButton = new OO.ui.ButtonWidget( { label: 'Publish changes…', flags: [ 'progressive', 'primary' ] } ); |
402 |
toolbars[ i ].$actions.append( toolbars[ i - 1 ].$element, publishButton.$element ); |
403 |
} |
404 |
|
405 |
actionButtonDelete = new OO.ui.ButtonWidget( { label: 'Delete', flags: [ 'destructive' ] } ); |
406 |
publishButton = new OO.ui.ButtonWidget( { label: 'Publish changes…', flags: [ 'progressive', 'primary' ] } ); |
407 |
actionGroup = new OO.ui.ButtonGroupWidget( { |
408 |
items: [ actionButtonDelete, publishButton, toolbars[ 6 ].items[ 0 ] ] |
409 |
} ); |
410 |
toolbars[ 7 ].$actions.append( actionGroup.$element ); |
411 |
|
412 |
for ( i = 0; i < toolbars.length; i++ ) { |
413 |
toolbars[ i ].emit( 'updateState' ); |
414 |
} |
415 |
|
416 |
// ToolGroups definition, in alphabetical/disabledAlphabetical order |
417 |
toolGroups = { |
418 |
// Parameters like in createTool() function above (starting with 'name') |
419 |
barTools: [ |
420 |
[ 'barTool', 'image', 'Basic tool in bar' ], |
421 |
[ 'disabledBarTool', 'image', 'Basic tool in bar disabled', setDisabled ] |
422 |
], |
423 |
|
424 |
disabledBarTools: [ |
425 |
[ 'barToolInDisabled', 'image', 'Basic tool in disabled bar' ] |
426 |
], |
427 |
|
428 |
cite: [ |
429 |
[ 'citeTool', 'quotes', 'Cite', null, null, true ] |
430 |
], |
431 |
|
432 |
citeDisabled: [ |
433 |
[ 'citeToolDisabled', 'quotes', 'Cite', setDisabled, null, true ] |
434 |
], |
435 |
|
436 |
editorSwitchTools: [ |
437 |
[ 'visualEditor', 'eye', 'Visual editing' ], |
438 |
[ 'wikitextEditor', 'wikiText', 'Source editing' ] |
439 |
], |
440 |
|
441 |
formatTools: [ |
442 |
[ 'paragraph', null, 'Paragraph' ], |
443 |
[ 'heading2', null, 'Heading 2' ], |
444 |
[ 'heading3', null, 'Sub-heading 1' ], |
445 |
[ 'heading4', null, 'Sub-heading 2' ], |
446 |
[ 'heading5', null, 'Sub-heading 3' ], |
447 |
[ 'heading6', null, 'Sub-heading 4' ], |
448 |
[ 'preformatted', null, 'Preformatted' ], |
449 |
[ 'blockquote', null, 'Blockquote' ] |
450 |
], |
451 |
|
452 |
history: [ |
453 |
[ 'undoTool', 'undo', 'Undo' ], |
454 |
[ 'redoTool', 'redo', 'Redo' ] |
455 |
], |
456 |
|
457 |
insertTools: [ |
458 |
[ 'media', 'image', 'First basic tool in list' ], |
459 |
[ 'template', 'puzzle', 'Template' ], |
460 |
[ 'table', 'table', 'Table' ], |
461 |
[ 'comment', 'speechBubble', 'Comment' ], |
462 |
[ 'hieroglyphs', null, 'Hieroglyphs' ], |
463 |
[ 'score', null, 'Musical notation' ], |
464 |
[ 'signature', 'signature', 'Your signature' ], |
465 |
[ 'gallery', 'imageGallery', 'Gallery' ], |
466 |
[ 'chem', null, 'Chemical formula' ], |
467 |
[ 'math', null, 'Math formula' ], |
468 |
[ 'syntaxHighlightDialog', 'markup', 'Code block' ], |
469 |
[ 'graph', null, 'Graph' ], |
470 |
[ 'referencesList', null, 'References list' ] |
471 |
], |
472 |
|
473 |
link: [ |
474 |
[ 'linkTool', 'link', 'Link' ] |
475 |
], |
476 |
|
477 |
listTools: [ |
478 |
[ 'listTool', 'image', 'First basic tool in list' ], |
479 |
[ 'listTool1', 'image', 'Basic tool in list' ], |
480 |
[ 'listTool3', 'image', 'Basic disabled tool in list', setDisabled ], |
481 |
[ 'listTool6', 'image', 'A final tool' ] |
482 |
], |
483 |
|
484 |
moreListTools: [ |
485 |
[ 'listTool2', 'code', 'Another basic tool' ], |
486 |
[ 'listTool4', 'image', 'More basic tools' ], |
487 |
[ 'listTool5', 'ellipsis', 'And even more' ] |
488 |
], |
489 |
|
490 |
disabledListTools: [ |
491 |
[ 'listToolInDisabled', 'image', 'Basic tool in disabled list' ] |
492 |
], |
493 |
|
494 |
autoDisableListTools: [ |
495 |
[ 'autoDisableListTool', 'image', 'Click to disable this tool', null, setDisabled ] |
496 |
], |
497 |
|
498 |
menuTools: [ |
499 |
[ 'menuTool', 'image', 'Basic tool' ], |
500 |
[ 'iconlessMenuTool', null, 'Tool without an icon' ], |
501 |
[ 'disabledMenuTool', 'image', 'Basic tool disabled', setDisabled ] |
502 |
], |
503 |
|
504 |
disabledMenuTools: [ |
505 |
[ 'menuToolInDisabled', 'image', 'Basic tool' ] |
506 |
], |
507 |
|
508 |
overflowTools: [ |
509 |
[ 'meta', 'window', 'Options' ], |
510 |
[ 'categories', 'image', 'Categories' ], |
511 |
[ 'settings', 'pageSettings', 'Page settings' ], |
512 |
[ 'advanced', 'advanced', 'Advanced settings' ], |
513 |
[ 'textLanguage', 'language', 'Languages' ], |
514 |
[ 'templatesUsed', 'puzzle', 'Templates used' ], |
515 |
[ 'codeMirror', 'highlight', 'Syntax highlighting', setDisabled ], |
516 |
[ 'changeDirectionality', 'textDirRTL', 'View as right-to-left' ], |
517 |
[ 'find', 'articleSearch', 'Find and replace' ] |
518 |
], |
519 |
|
520 |
specialCharacters: [ |
521 |
[ 'specialCharacter', 'specialCharacter', 'Special character' ] |
522 |
], |
523 |
|
524 |
popupTools: [ |
525 |
[ 'popupTool', 'alertTool' ] |
526 |
], |
527 |
|
528 |
structureTools: [ |
529 |
[ 'bullet', 'listBullet', 'Bullet list' ], |
530 |
[ 'number', 'listNumbered', 'Numbered list' ], |
531 |
[ 'outdent', 'outdent', 'Decrease indentation' ], |
532 |
[ 'indent', 'indent', 'Increase indentation' ] |
533 |
], |
534 |
|
535 |
styleTools: [ |
536 |
[ 'bold', 'bold', 'Bold' ], |
537 |
[ 'italic', 'italic', 'Italic' ], |
538 |
[ 'italic', 'italic', 'Italic' ], |
539 |
[ 'superscript', 'superscript', 'Superscript' ], |
540 |
[ 'subscript', 'subscript', 'Subscript' ], |
541 |
[ 'strikethrough', 'strikethrough', 'Strikethrough' ], |
542 |
[ 'code', 'code', 'Computer Code' ], |
543 |
[ 'underline', 'underline', 'Underline' ], |
544 |
[ 'language', 'language', 'Language' ], |
545 |
[ 'big', 'bigger', 'Big' ], |
546 |
[ 'small', 'smaller', 'Small' ], |
547 |
[ 'clear', 'cancel', 'Clear Styling', setDisabled ] |
548 |
], |
549 |
|
550 |
unusedStuff: [ |
551 |
[ 'unusedTool', 'help', 'This tool is not explicitly used anywhere' ], |
552 |
[ 'unusedTool1', 'help', 'And neither is this one' ] |
553 |
] |
554 |
}; |
555 |
|
556 |
// ToolGroup creation, in Toolbar numeric and ToolGroup alphabetical order |
557 |
createToolGroup( 0, 'barTools' ); |
558 |
createToolGroup( 0, 'disabledBarTools' ); |
559 |
createToolGroup( 0, 'listTools' ); |
560 |
createToolGroup( 0, 'moreListTools' ); |
561 |
createToolGroup( 0, 'disabledListTools' ); |
562 |
createToolGroup( 0, 'autoDisableListTools' ); |
563 |
createToolGroup( 0, 'unusedStuff' ); |
564 |
|
565 |
createToolGroup( 1, 'cite' ); |
566 |
createToolGroup( 1, 'citeDisabled' ); |
567 |
createToolGroup( 1, 'menuTools' ); |
568 |
createToolGroup( 1, 'disabledMenuTools' ); |
569 |
|
570 |
createToolGroup( 6, 'listTools' ); |
571 |
|
572 |
createToolGroup( 7, 'menuTools' ); |
573 |
createToolGroup( 7, 'disabledMenuTools' ); |
574 |
|
575 |
for ( i = 3; i <= 5; i += 2 ) { |
576 |
createToolGroup( i - 1, 'overflowTools' ); |
577 |
createToolGroup( i - 1, 'editorSwitchTools' ); |
578 |
createToolGroup( i, 'cite' ); |
579 |
createToolGroup( i, 'formatTools' ); |
580 |
createToolGroup( i, 'insertTools' ); |
581 |
createToolGroup( i, 'history' ); |
582 |
createToolGroup( i, 'link' ); |
583 |
createToolGroup( i, 'listTools' ); |
584 |
createToolGroup( i, 'moreListTools' ); |
585 |
createToolGroup( i, 'autoDisableListTools' ); |
586 |
createToolGroup( i, 'menuTools' ); |
587 |
createToolGroup( i, 'specialCharacters' ); |
588 |
createToolGroup( i, 'structureTools' ); |
589 |
createToolGroup( i, 'styleTools' ); |
590 |
createToolGroup( i, 'unusedStuff' ); |
591 |
} |
592 |
|
593 |
for ( i = 0; i < toolbars.length; i++ ) { |
594 |
if ( i === 2 || i === 4 || i === 6 ) { |
595 |
// Action toolbars |
596 |
continue; |
597 |
} |
598 |
$containers = $containers.add( |
599 |
new OO.ui.PanelLayout( { |
600 |
expanded: false, |
601 |
framed: true |
602 |
} ).$element |
603 |
.addClass( 'demo-toolbar' ) |
604 |
); |
605 |
|
606 |
$containers.last().append( toolbars[ i ].$element ); |
607 |
} |
608 |
$containers.append( '' ); |
609 |
$demo.append( |
610 |
new OO.ui.PanelLayout( { |
611 |
expanded: false, |
612 |
framed: false |
613 |
} ).$element |
614 |
.addClass( 'demo-container demo-toolbars' ) |
615 |
.attr( 'role', 'main' ) |
616 |
.append( |
617 |
$containers.eq( 0 ).append( '<div class="demo-toolbars-contents">Toolbar</div>' ), |
618 |
$containers.eq( 1 ).append( '<div class="demo-toolbars-contents">Toolbar with action buttons</div>' ), |
619 |
$containers.eq( 2 ).append( '<div class="demo-toolbars-contents">Word processor toolbar</div>' ), |
620 |
$containers.eq( 3 ).prepend( '<div class="demo-toolbars-contents">Word processor toolbar set to <code>position: 'bottom'</code></div>' ), |
621 |
$containers.eq( 4 ).append( '<div class="demo-toolbars-contents">Toolbar with action buttons in a group</div>' ) |
622 |
) |
623 |
); |
624 |
for ( i = 0; i < toolbars.length; i++ ) { |
625 |
toolbars[ i ].initialize(); |
626 |
} |
627 |
}; |
628 |
|
|
|
/demos/pages/widgets.js
|
32 problems (32 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 10, Column 14: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 10, Column 14: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 1135, Column 19: "Prefer createHTMLDocument to $.parseHTML"
jquery/no-parse-html
|
Error |
Row 1139, Column 19: "Prefer createHTMLDocument to $.parseHTML"
jquery/no-parse-html
|
Error |
Row 1143, Column 19: "Prefer createHTMLDocument to $.parseHTML"
jquery/no-parse-html
|
Error |
Row 1929, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 1946, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 1963, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 1980, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 1997, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2030, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2045, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2060, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2075, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2091, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2107, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2123, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2139, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2155, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2414, Column 14: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2706, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2707, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2708, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2709, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2710, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2711, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2712, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2755, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2756, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2757, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 2944, Column 2: "Prefer Array#forEach to $.each"
jquery/no-each
|
Error |
Row 2954, Column 3: "Prefer Array#forEach to $.each"
jquery/no-each
|
Line |
Source |
1 |
Demo.static.pages.widgets = function ( demo ) { |
2 |
var i, fieldsets, |
3 |
loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, ' + |
4 |
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\u200E', |
5 |
textInputForLabel, labelForTextInput, radioSelectInputForLabel, labelForRadioSelectInput, |
6 |
inputForValidation, fieldLayoutForValidation, |
7 |
horizontalDragItems = [], |
8 |
verticalDragItems = [], |
9 |
verticalHandledDragItems = [], |
Error |
Row 10, Column 14: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 10, Column 14: "Prefer classList to $.addClass"
jquery/no-class
|
10 |
$overlay = $( '<div>' ).addClass( 'demo-overlay' ).attr( 'id', 'demo-overlay' ), |
11 |
$demo = demo.$element, |
12 |
fixedItemsTagMultiselectWidget = new OO.ui.TagMultiselectWidget( { |
13 |
placeholder: 'Add tags', |
14 |
allowArbitrary: true |
15 |
} ); |
16 |
|
17 |
fixedItemsTagMultiselectWidget.addTag( 'item1', 'Item 1 (optional)' ); |
18 |
fixedItemsTagMultiselectWidget.addTag( 'item2', 'Item 2 (mandatory)' ); |
19 |
fixedItemsTagMultiselectWidget.findItemFromData( 'item2' ).setFixed( true ); |
20 |
|
21 |
for ( i = 0; i <= 12; i++ ) { |
22 |
horizontalDragItems.push( |
23 |
new Demo.DraggableItemWidget( { |
24 |
data: 'item' + i, |
25 |
icon: 'tag', |
26 |
label: 'Inline item ' + i |
27 |
} ) |
28 |
); |
29 |
if ( i <= 6 ) { |
30 |
verticalDragItems.push( |
31 |
new Demo.DraggableItemWidget( { |
32 |
data: 'item' + i, |
33 |
icon: 'tag', |
34 |
label: 'Item ' + i |
35 |
} ) |
36 |
); |
37 |
verticalHandledDragItems.push( |
38 |
new Demo.DraggableHandledItemWidget( { |
39 |
data: 'item' + i, |
40 |
icon: 'draggable', |
41 |
label: 'Item ' + i |
42 |
} ) |
43 |
); |
44 |
} |
45 |
} |
46 |
|
47 |
textInputForLabel = new OO.ui.TextInputWidget( { value: 'Input for label above' } ); |
48 |
labelForTextInput = new OO.ui.LabelWidget( { |
49 |
label: 'Label for TextInputWidget below', |
50 |
input: textInputForLabel |
51 |
} ); |
52 |
|
53 |
radioSelectInputForLabel = new OO.ui.RadioSelectInputWidget( { |
54 |
options: [ |
55 |
{ |
56 |
data: 'a', |
57 |
label: 'Input for label above' |
58 |
}, |
59 |
{ |
60 |
data: 'b', |
61 |
label: 'Input for label above' |
62 |
} |
63 |
] |
64 |
} ); |
65 |
labelForRadioSelectInput = new OO.ui.LabelWidget( { |
66 |
label: 'Label for RadioSelectInputWidget below', |
67 |
input: radioSelectInputForLabel |
68 |
} ); |
69 |
|
70 |
inputForValidation = new OO.ui.TextInputWidget( { |
71 |
validate: function ( value ) { |
72 |
return value.length % 2 === 0; |
73 |
} |
74 |
} ); |
75 |
|
76 |
fieldLayoutForValidation = new OO.ui.FieldLayout( inputForValidation, { |
77 |
align: 'top', |
78 |
label: 'FieldLayout aligned top with validation errors', |
79 |
help: 'Enter only even number of characters' |
80 |
} ); |
81 |
|
82 |
inputForValidation.$input.on( 'blur', function () { |
83 |
inputForValidation.getValidity().then( function () { |
84 |
fieldLayoutForValidation.setErrors( [] ); |
85 |
}, function () { |
86 |
fieldLayoutForValidation.setErrors( [ |
87 |
'Please enter an even number of characters' |
88 |
] ); |
89 |
} ); |
90 |
} ); |
91 |
|
92 |
fieldsets = [ |
93 |
new OO.ui.FieldsetLayout( { |
94 |
id: 'demo-section-buttons', |
95 |
label: 'Buttons', |
96 |
items: [ |
97 |
new OO.ui.FieldLayout( |
98 |
new OO.ui.ButtonWidget( { label: 'Normal' } ), |
99 |
{ |
100 |
label: 'ButtonWidget (normal)\u200E', |
101 |
align: 'top' |
102 |
} |
103 |
), |
104 |
new OO.ui.FieldLayout( |
105 |
new OO.ui.ButtonWidget( { |
106 |
label: 'Progressive', |
107 |
flags: [ 'progressive' ] |
108 |
} ), |
109 |
{ |
110 |
label: 'ButtonWidget (progressive)\u200E', |
111 |
align: 'top' |
112 |
} |
113 |
), |
114 |
new OO.ui.FieldLayout( |
115 |
new OO.ui.ButtonWidget( { |
116 |
label: 'Destructive', |
117 |
flags: [ 'destructive' ] |
118 |
} ), |
119 |
{ |
120 |
label: 'ButtonWidget (destructive)\u200E', |
121 |
align: 'top' |
122 |
} |
123 |
), |
124 |
new OO.ui.FieldLayout( |
125 |
new OO.ui.ButtonWidget( { |
126 |
label: 'Primary progressive', |
127 |
flags: [ 'primary', 'progressive' ] |
128 |
} ), |
129 |
{ |
130 |
label: 'ButtonWidget (primary, progressive)\u200E', |
131 |
align: 'top' |
132 |
} |
133 |
), |
134 |
new OO.ui.FieldLayout( |
135 |
new OO.ui.ButtonWidget( { |
136 |
label: 'Primary destructive', |
137 |
flags: [ 'primary', 'destructive' ] |
138 |
} ), |
139 |
{ |
140 |
label: 'ButtonWidget (primary, destructive)\u200E', |
141 |
align: 'top' |
142 |
} |
143 |
), |
144 |
new OO.ui.FieldLayout( |
145 |
new OO.ui.ButtonWidget( { |
146 |
label: 'Disabled', |
147 |
disabled: true |
148 |
} ), |
149 |
{ |
150 |
label: 'ButtonWidget (disabled)\u200E', |
151 |
align: 'top' |
152 |
} |
153 |
), |
154 |
new OO.ui.FieldLayout( |
155 |
new OO.ui.ButtonWidget( { |
156 |
label: 'Progressive', |
157 |
icon: 'tag', |
158 |
flags: [ 'progressive' ], |
159 |
disabled: true |
160 |
} ), |
161 |
{ |
162 |
label: 'ButtonWidget (progressive, icon, disabled)\u200E', |
163 |
align: 'top' |
164 |
} |
165 |
), |
166 |
new OO.ui.FieldLayout( |
167 |
new OO.ui.ButtonWidget( { |
168 |
label: 'Icon', |
169 |
icon: 'tag' |
170 |
} ), |
171 |
{ |
172 |
label: 'ButtonWidget (icon)\u200E', |
173 |
align: 'top' |
174 |
} |
175 |
), |
176 |
new OO.ui.FieldLayout( |
177 |
new OO.ui.ButtonWidget( { |
178 |
label: 'Icon', |
179 |
icon: 'tag', |
180 |
flags: [ 'progressive' ] |
181 |
} ), |
182 |
{ |
183 |
label: 'ButtonWidget (icon, progressive)\u200E', |
184 |
align: 'top' |
185 |
} |
186 |
), |
187 |
new OO.ui.FieldLayout( |
188 |
new OO.ui.ButtonWidget( { |
189 |
label: 'Indicator', |
190 |
indicator: 'down' |
191 |
} ), |
192 |
{ |
193 |
label: 'ButtonWidget (indicator)\u200E', |
194 |
align: 'top' |
195 |
} |
196 |
), |
197 |
new OO.ui.FieldLayout( |
198 |
new OO.ui.ButtonWidget( { |
199 |
label: 'Indicator', |
200 |
indicator: 'down', |
201 |
flags: [ 'progressive' ] |
202 |
} ), |
203 |
{ |
204 |
label: 'ButtonWidget (indicator, progressive)\u200E', |
205 |
align: 'top' |
206 |
} |
207 |
), |
208 |
new OO.ui.FieldLayout( |
209 |
new OO.ui.ButtonWidget( { |
210 |
label: 'Access key: G', |
211 |
accessKey: 'g' |
212 |
} ), |
213 |
{ |
214 |
label: 'ButtonWidget (with accesskey)\u200E', |
215 |
align: 'top', |
216 |
help: new OO.ui.HtmlSnippet( 'Notice: Using `accesskey` might <a href="http://webaim.org/techniques/keyboard/accesskey" target="_blank">negatively impact screen readers</a>!' ) |
217 |
} |
218 |
), |
219 |
new OO.ui.FieldLayout( |
220 |
new OO.ui.ButtonWidget( { |
221 |
icon: 'help', |
222 |
title: 'Icon only, framed' |
223 |
} ), |
224 |
{ |
225 |
label: 'ButtonWidget (icon only)\u200E', |
226 |
align: 'top' |
227 |
} |
228 |
), |
229 |
new OO.ui.FieldLayout( |
230 |
new OO.ui.ButtonWidget( { |
231 |
indicator: 'clear', |
232 |
title: 'Indicator only, framed' |
233 |
} ), |
234 |
{ |
235 |
label: 'ButtonWidget (indicator only)\u200E', |
236 |
align: 'top' |
237 |
} |
238 |
), |
239 |
new OO.ui.FieldLayout( |
240 |
new OO.ui.ButtonWidget( { |
241 |
framed: false, |
242 |
icon: 'help', |
243 |
title: 'Icon only' |
244 |
} ), |
245 |
{ |
246 |
label: 'ButtonWidget (frameless, icon only)\u200E', |
247 |
align: 'top' |
248 |
} |
249 |
), |
250 |
new OO.ui.FieldLayout( |
251 |
new OO.ui.ButtonWidget( { |
252 |
framed: false, |
253 |
indicator: 'clear', |
254 |
title: 'Indicator only' |
255 |
} ), |
256 |
{ |
257 |
label: 'ButtonWidget (frameless, indicator only)\u200E', |
258 |
align: 'top' |
259 |
} |
260 |
), |
261 |
new OO.ui.FieldLayout( |
262 |
new OO.ui.ButtonWidget( { |
263 |
framed: false, |
264 |
icon: 'tag', |
265 |
label: 'Labeled' |
266 |
} ), |
267 |
{ |
268 |
label: 'ButtonWidget (frameless)\u200E', |
269 |
align: 'top' |
270 |
} |
271 |
), |
272 |
new OO.ui.FieldLayout( |
273 |
new OO.ui.ButtonWidget( { |
274 |
framed: false, |
275 |
flags: [ 'progressive' ], |
276 |
icon: 'check', |
277 |
label: 'Progressive' |
278 |
} ), |
279 |
{ |
280 |
label: 'ButtonWidget (frameless, progressive)\u200E', |
281 |
align: 'top' |
282 |
} |
283 |
), |
284 |
new OO.ui.FieldLayout( |
285 |
new OO.ui.ButtonWidget( { |
286 |
framed: false, |
287 |
flags: [ 'warning' ], |
288 |
icon: 'alert', |
289 |
label: 'Warning' |
290 |
} ), |
291 |
{ |
292 |
label: 'ButtonWidget (frameless, warning)\u200E', |
293 |
align: 'top' |
294 |
} |
295 |
), |
296 |
new OO.ui.FieldLayout( |
297 |
new OO.ui.ButtonWidget( { |
298 |
framed: false, |
299 |
flags: [ 'destructive' ], |
300 |
icon: 'trash', |
301 |
label: 'Destructive' |
302 |
} ), |
303 |
{ |
304 |
label: 'ButtonWidget (frameless, destructive)\u200E', |
305 |
align: 'top' |
306 |
} |
307 |
), |
308 |
new OO.ui.FieldLayout( |
309 |
new OO.ui.ButtonWidget( { |
310 |
framed: false, |
311 |
flags: [ 'destructive' ], |
312 |
label: 'Cancel' |
313 |
} ), |
314 |
{ |
315 |
label: 'ButtonWidget (frameless, label-only, destructive)\u200E', |
316 |
align: 'top' |
317 |
} |
318 |
), |
319 |
new OO.ui.FieldLayout( |
320 |
new OO.ui.ButtonWidget( { |
321 |
framed: false, |
322 |
icon: 'tag', |
323 |
label: 'Disabled', |
324 |
disabled: true |
325 |
} ), |
326 |
{ |
327 |
label: 'ButtonWidget (frameless, disabled)\u200E', |
328 |
align: 'top' |
329 |
} |
330 |
), |
331 |
new OO.ui.FieldLayout( |
332 |
new OO.ui.ButtonWidget( { |
333 |
framed: false, |
334 |
icon: 'tag', |
335 |
indicator: 'down', |
336 |
label: 'Labeled' |
337 |
} ), |
338 |
{ |
339 |
label: 'ButtonWidget (frameless, icon & label & indicator)\u200E', |
340 |
align: 'top' |
341 |
} |
342 |
), |
343 |
new OO.ui.FieldLayout( |
344 |
new OO.ui.ButtonWidget( { |
345 |
framed: false, |
346 |
indicator: 'down', |
347 |
label: 'Labeled' |
348 |
} ), |
349 |
{ |
350 |
label: 'ButtonWidget (frameless, label & indicator)\u200E', |
351 |
align: 'top' |
352 |
} |
353 |
), |
354 |
new OO.ui.FieldLayout( |
355 |
new OO.ui.ButtonWidget( { |
356 |
framed: false, |
357 |
icon: 'tag', |
358 |
indicator: 'down', |
359 |
title: 'Icon & indicator' |
360 |
} ), |
361 |
{ |
362 |
label: 'ButtonWidget (frameless, icon & indicator)\u200E', |
363 |
align: 'top' |
364 |
} |
365 |
), |
366 |
new OO.ui.FieldLayout( |
367 |
new OO.ui.ButtonInputWidget( { |
368 |
label: 'Submit the form', |
369 |
type: 'submit', |
370 |
flags: [ 'primary', 'progressive' ], |
371 |
useInputTag: true |
372 |
} ), |
373 |
{ |
374 |
align: 'top', |
375 |
label: 'ButtonInputWidget (using <input>)\u200E' |
376 |
} |
377 |
), |
378 |
new OO.ui.FieldLayout( |
379 |
new OO.ui.ButtonInputWidget( { |
380 |
label: 'Another button', |
381 |
type: 'button' |
382 |
} ), |
383 |
{ |
384 |
align: 'top', |
385 |
label: 'ButtonInputWidget (using <button>)\u200E' |
386 |
} |
387 |
), |
388 |
new OO.ui.FieldLayout( |
389 |
new OO.ui.ButtonInputWidget( { |
390 |
title: 'Accesskey is added to the title.', |
391 |
label: 'Access key: H', |
392 |
accessKey: 'h' |
393 |
} ), |
394 |
{ |
395 |
label: 'ButtonInputWidget (with accesskey & title)\u200E', |
396 |
align: 'top', |
397 |
help: new OO.ui.HtmlSnippet( 'Notice: Using `accesskey` might <a href="http://webaim.org/techniques/keyboard/accesskey" target="_blank">negatively impact screen readers</a>!' ) |
398 |
} |
399 |
), |
400 |
new OO.ui.FieldLayout( |
401 |
new OO.ui.ButtonInputWidget( { |
402 |
framed: false, |
403 |
label: 'Another button', |
404 |
type: 'button' |
405 |
} ), |
406 |
{ |
407 |
align: 'top', |
408 |
label: 'ButtonInputWidget (frameless, using <button>)\u200E' |
409 |
} |
410 |
), |
411 |
new OO.ui.FieldLayout( |
412 |
new OO.ui.ButtonInputWidget( { |
413 |
framed: false, |
414 |
label: 'Another button', |
415 |
type: 'button', |
416 |
useInputTag: true |
417 |
} ), |
418 |
{ |
419 |
align: 'top', |
420 |
label: 'ButtonInputWidget (frameless, using <input>)\u200E' |
421 |
} |
422 |
), |
423 |
new OO.ui.FieldLayout( |
424 |
new OO.ui.ToggleButtonWidget( { label: 'Toggle' } ), |
425 |
{ |
426 |
label: 'ToggleButtonWidget', |
427 |
align: 'top' |
428 |
} |
429 |
), |
430 |
new OO.ui.FieldLayout( |
431 |
new OO.ui.ToggleButtonWidget( { label: 'Toggle', value: true } ), |
432 |
{ |
433 |
label: 'ToggleButtonWidget (initially active)\u200E', |
434 |
align: 'top' |
435 |
} |
436 |
), |
437 |
new OO.ui.FieldLayout( |
438 |
new OO.ui.ToggleButtonWidget( { icon: 'next' } ), |
439 |
{ |
440 |
label: 'ToggleButtonWidget (icon only)\u200E', |
441 |
align: 'top' |
442 |
} |
443 |
), |
444 |
new OO.ui.FieldLayout( |
445 |
new OO.ui.ToggleButtonWidget( { icon: 'next', value: true } ), |
446 |
{ |
447 |
label: 'ToggleButtonWidget (icon only, initially active)\u200E', |
448 |
align: 'top' |
449 |
} |
450 |
) |
451 |
] |
452 |
} ), |
453 |
new OO.ui.FieldsetLayout( { |
454 |
id: 'demo-section-button-sets', |
455 |
label: 'Button sets', |
456 |
items: [ |
457 |
new OO.ui.FieldLayout( |
458 |
new OO.ui.ButtonGroupWidget( { |
459 |
items: [ |
460 |
new OO.ui.ButtonWidget( { |
461 |
icon: 'tag', |
462 |
label: 'One' |
463 |
} ), |
464 |
new OO.ui.ButtonWidget( { |
465 |
label: 'Two' |
466 |
} ), |
467 |
new OO.ui.ButtonWidget( { |
468 |
indicator: 'clear', |
469 |
label: 'Three' |
470 |
} ) |
471 |
] |
472 |
} ), |
473 |
{ |
474 |
label: 'ButtonGroupWidget', |
475 |
align: 'top' |
476 |
} |
477 |
), |
478 |
new OO.ui.FieldLayout( |
479 |
new OO.ui.ButtonGroupWidget( { |
480 |
items: [ |
481 |
new OO.ui.ButtonWidget( { |
482 |
label: 'One', |
483 |
flags: [ 'destructive' ] |
484 |
} ), |
485 |
new OO.ui.ButtonWidget( { |
486 |
label: 'Two', |
487 |
flags: [ 'progressive' ] |
488 |
} ) |
489 |
] |
490 |
} ), |
491 |
{ |
492 |
label: 'ButtonGroupWidget (destructive and progressive ButtonWidget)', |
493 |
align: 'top' |
494 |
} |
495 |
), |
496 |
new OO.ui.FieldLayout( |
497 |
new OO.ui.ButtonGroupWidget( { |
498 |
items: [ |
499 |
new OO.ui.ButtonWidget( { |
500 |
icon: 'tag', |
501 |
flags: [ 'destructive' ] |
502 |
} ), |
503 |
new OO.ui.ButtonWidget( { |
504 |
label: 'Two', |
505 |
flags: [ 'progressive' ] |
506 |
} ) |
507 |
] |
508 |
} ), |
509 |
{ |
510 |
label: 'ButtonGroupWidget (destructive icon and progressive text)\u200E', |
511 |
align: 'top' |
512 |
} |
513 |
), |
514 |
new OO.ui.FieldLayout( |
515 |
new OO.ui.ButtonGroupWidget( { |
516 |
items: [ |
517 |
new OO.ui.ToggleButtonWidget( { |
518 |
icon: 'tag', |
519 |
label: 'One' |
520 |
} ), |
521 |
new OO.ui.ToggleButtonWidget( { |
522 |
label: 'Two' |
523 |
} ), |
524 |
new OO.ui.ToggleButtonWidget( { |
525 |
indicator: 'clear', |
526 |
label: 'Three' |
527 |
} ) |
528 |
] |
529 |
} ), |
530 |
{ |
531 |
label: 'ButtonGroupWidget with ToggleButtonWidgets', |
532 |
align: 'top' |
533 |
} |
534 |
), |
535 |
new OO.ui.FieldLayout( |
536 |
new OO.ui.ButtonSelectWidget( { |
537 |
items: [ |
538 |
new OO.ui.ButtonOptionWidget( { |
539 |
data: 'b', |
540 |
icon: 'tag', |
541 |
label: 'One' |
542 |
} ), |
543 |
new OO.ui.ButtonOptionWidget( { |
544 |
data: 'c', |
545 |
label: 'Two' |
546 |
} ), |
547 |
new OO.ui.ButtonOptionWidget( { |
548 |
data: 'd', |
549 |
indicator: 'clear', |
550 |
label: 'Three' |
551 |
} ) |
552 |
] |
553 |
} ), |
554 |
{ |
555 |
label: 'ButtonSelectWidget', |
556 |
align: 'top' |
557 |
} |
558 |
), |
559 |
new OO.ui.FieldLayout( |
560 |
new OO.ui.ButtonSelectWidget( { |
561 |
disabled: true, |
562 |
items: [ |
563 |
new OO.ui.ButtonOptionWidget( { |
564 |
data: 'b', |
565 |
icon: 'tag', |
566 |
label: 'One' |
567 |
} ), |
568 |
new OO.ui.ButtonOptionWidget( { |
569 |
data: 'c', |
570 |
label: 'Two' |
571 |
} ), |
572 |
new OO.ui.ButtonOptionWidget( { |
573 |
data: 'd', |
574 |
indicator: 'clear', |
575 |
label: 'Three' |
576 |
} ) |
577 |
] |
578 |
} ), |
579 |
{ |
580 |
label: 'ButtonSelectWidget (disabled)\u200E', |
581 |
align: 'top' |
582 |
} |
583 |
), |
584 |
new OO.ui.FieldLayout( |
585 |
new OO.ui.ButtonSelectWidget( { |
586 |
items: [ |
587 |
new OO.ui.ButtonOptionWidget( { |
588 |
data: 'b', |
589 |
icon: 'tag', |
590 |
label: 'One', |
591 |
disabled: true |
592 |
} ), |
593 |
new OO.ui.ButtonOptionWidget( { |
594 |
data: 'c', |
595 |
label: 'Two' |
596 |
} ), |
597 |
new OO.ui.ButtonOptionWidget( { |
598 |
data: 'd', |
599 |
indicator: 'clear', |
600 |
label: 'Three' |
601 |
} ) |
602 |
] |
603 |
} ), |
604 |
{ |
605 |
label: 'ButtonSelectWidget (disabled items)\u200E', |
606 |
align: 'top' |
607 |
} |
608 |
), |
609 |
new OO.ui.FieldLayout( |
610 |
new OO.ui.ButtonSelectWidget( { |
611 |
items: [ |
612 |
new OO.ui.ButtonOptionWidget( { |
613 |
data: 'a', |
614 |
label: 'Access key: I', |
615 |
accessKey: 'i' |
616 |
} ), |
617 |
new OO.ui.ButtonOptionWidget( { |
618 |
data: 'b', |
619 |
label: 'Access key: J', |
620 |
accessKey: 'j' |
621 |
} ), |
622 |
new OO.ui.ButtonOptionWidget( { |
623 |
data: 'c', |
624 |
label: 'Access key: K', |
625 |
accessKey: 'k' |
626 |
} ) |
627 |
] |
628 |
} ), |
629 |
{ |
630 |
label: 'ButtonSelectWidget (with accesskeys)\u200E', |
631 |
align: 'top' |
632 |
} |
633 |
) |
634 |
] |
635 |
} ), |
636 |
new OO.ui.FieldsetLayout( { |
637 |
id: 'demo-section-button-showcase', |
638 |
label: 'Button style showcase', |
639 |
items: [ |
640 |
new OO.ui.FieldLayout( |
641 |
new Demo.ButtonStyleShowcaseWidget(), |
642 |
{ |
643 |
align: 'top' |
644 |
} |
645 |
) |
646 |
] |
647 |
} ), |
648 |
new OO.ui.FieldsetLayout( { |
649 |
id: 'demo-section-inputs', |
650 |
label: 'Inputs: TextInput, TextInput, MultilineTextInput, SearchInput, NumberInput', |
651 |
items: [ |
652 |
new OO.ui.FieldLayout( |
653 |
new OO.ui.TextInputWidget( { value: 'Text input' } ), |
654 |
{ |
655 |
label: 'TextInputWidget\u200E', |
656 |
align: 'top' |
657 |
} |
658 |
), |
659 |
new OO.ui.FieldLayout( |
660 |
new OO.ui.TextInputWidget( { icon: 'help' } ), |
661 |
{ |
662 |
label: 'TextInputWidget (icon)\u200E', |
663 |
align: 'top' |
664 |
} |
665 |
), |
666 |
new OO.ui.FieldLayout( |
667 |
new OO.ui.TextInputWidget( { |
668 |
required: true, |
669 |
validate: 'non-empty' |
670 |
} ), |
671 |
{ |
672 |
label: 'TextInputWidget (required)\u200E', |
673 |
align: 'top' |
674 |
} |
675 |
), |
676 |
new OO.ui.FieldLayout( |
677 |
new OO.ui.TextInputWidget( { |
678 |
validate: function ( value ) { |
679 |
return value.length % 2 === 0; |
680 |
} |
681 |
} ), |
682 |
{ |
683 |
label: 'TextInputWidget (only allows even number of characters)\u200E', |
684 |
align: 'top' |
685 |
} |
686 |
), |
687 |
new OO.ui.FieldLayout( |
688 |
new OO.ui.TextInputWidget( { placeholder: 'Placeholder' } ), |
689 |
{ |
690 |
label: 'TextInputWidget (placeholder)\u200E', |
691 |
align: 'top' |
692 |
} |
693 |
), |
694 |
new OO.ui.FieldLayout( |
695 |
new OO.ui.TextInputWidget( { |
696 |
value: 'Title attribute', |
697 |
title: 'Title attribute with more information about me.' |
698 |
} ), |
699 |
{ |
700 |
label: 'TextInputWidget (with title)\u200E', |
701 |
align: 'top' |
702 |
} |
703 |
), |
704 |
new OO.ui.FieldLayout( |
705 |
new OO.ui.TextInputWidget( { |
706 |
value: 'Readonly', |
707 |
readOnly: true |
708 |
} ), |
709 |
{ |
710 |
label: 'TextInputWidget (readonly)\u200E', |
711 |
align: 'top' |
712 |
} |
713 |
), |
714 |
new OO.ui.FieldLayout( |
715 |
new OO.ui.TextInputWidget( { |
716 |
value: 'Disabled', |
717 |
disabled: true |
718 |
} ), |
719 |
{ |
720 |
label: 'TextInputWidget (disabled)\u200E', |
721 |
align: 'top' |
722 |
} |
723 |
), |
724 |
new OO.ui.FieldLayout( |
725 |
new OO.ui.MultilineTextInputWidget( { |
726 |
value: 'Multiline\nMultiline' |
727 |
} ), |
728 |
{ |
729 |
label: 'MultilineTextInputWidget\u200E', |
730 |
align: 'top' |
731 |
} |
732 |
), |
733 |
new OO.ui.FieldLayout( |
734 |
new OO.ui.MultilineTextInputWidget( { |
735 |
rows: 15, |
736 |
value: 'Multiline\nMultiline' |
737 |
} ), |
738 |
{ |
739 |
label: 'MultilineTextInputWidget (rows=15)\u200E', |
740 |
align: 'top' |
741 |
} |
742 |
), |
743 |
new OO.ui.FieldLayout( |
744 |
new OO.ui.MultilineTextInputWidget( { |
745 |
autosize: true, |
746 |
value: 'Autosize\nAutosize\nAutosize\nAutosize' |
747 |
} ), |
748 |
{ |
749 |
label: 'MultilineTextInputWidget (autosize)\u200E', |
750 |
align: 'top' |
751 |
} |
752 |
), |
753 |
new OO.ui.FieldLayout( |
754 |
new OO.ui.MultilineTextInputWidget( { |
755 |
rows: 10, |
756 |
autosize: true, |
757 |
value: 'Autosize\nAutosize\nAutosize\nAutosize' |
758 |
} ), |
759 |
{ |
760 |
label: 'MultilineTextInputWidget (autosize, rows=10)\u200E', |
761 |
align: 'top' |
762 |
} |
763 |
), |
764 |
new OO.ui.FieldLayout( |
765 |
new OO.ui.MultilineTextInputWidget( { |
766 |
autosize: true, |
767 |
icon: 'tag', |
768 |
indicator: 'clear', |
769 |
label: 'Inline label', |
770 |
value: 'Autosize\nAutosize\nAutosize\nAutosize' |
771 |
} ), |
772 |
{ |
773 |
label: 'MultilineTextInputWidget (autosize, icon, indicator, label)\u200E', |
774 |
align: 'top' |
775 |
} |
776 |
), |
777 |
new OO.ui.FieldLayout( |
778 |
new OO.ui.TextInputWidget( { |
779 |
value: 'Text input with label', |
780 |
label: 'Inline label' |
781 |
} ), |
782 |
{ |
783 |
label: 'TextInputWidget (label)\u200E', |
784 |
align: 'top' |
785 |
} |
786 |
), |
787 |
new OO.ui.FieldLayout( |
788 |
new OO.ui.TextInputWidget( { |
789 |
value: 'Text input with label', |
790 |
label: 'Inline label', |
791 |
labelPosition: 'before' |
792 |
} ), |
793 |
{ |
794 |
label: 'TextInputWidget (label[position=before])\u200E', |
795 |
align: 'top' |
796 |
} |
797 |
), |
798 |
new OO.ui.FieldLayout( |
799 |
new OO.ui.TextInputWidget( { |
800 |
icon: 'tag', |
801 |
indicator: 'clear', |
802 |
value: 'Text input with label', |
803 |
label: 'Inline label' |
804 |
} ), |
805 |
{ |
806 |
label: 'TextInputWidget (icon, indicator, label)\u200E', |
807 |
align: 'top' |
808 |
} |
809 |
), |
810 |
new OO.ui.FieldLayout( |
811 |
new OO.ui.TextInputWidget( { |
812 |
icon: 'tag', |
813 |
indicator: 'clear', |
814 |
value: 'Text input with label', |
815 |
label: 'Inline label', |
816 |
labelPosition: 'before' |
817 |
} ), |
818 |
{ |
819 |
label: 'TextInputWidget (icon, indicator, label[position=before])\u200E', |
820 |
align: 'top' |
821 |
} |
822 |
), |
823 |
new OO.ui.FieldLayout( |
824 |
new OO.ui.TextInputWidget( { |
825 |
value: 'Disabled', |
826 |
icon: 'tag', |
827 |
indicator: 'clear', |
828 |
label: 'Inline label', |
829 |
disabled: true |
830 |
} ), |
831 |
{ |
832 |
label: 'TextInputWidget (icon, indicator, label, disabled)\u200E', |
833 |
align: 'top' |
834 |
} |
835 |
), |
836 |
new OO.ui.FieldLayout( |
837 |
new OO.ui.TextInputWidget( { |
838 |
value: 'Access key: S', |
839 |
accessKey: 's' |
840 |
} ), |
841 |
{ |
842 |
label: 'TextInputWidget (with accesskey)\u200E', |
843 |
align: 'top', |
844 |
help: new OO.ui.HtmlSnippet( 'Notice: Using `accesskey` might <a href="http://webaim.org/techniques/keyboard/accesskey" target="_blank">negatively impact screen readers</a>!' ) |
845 |
} |
846 |
), |
847 |
new OO.ui.FieldLayout( |
848 |
new Demo.DynamicLabelTextInputWidget( { |
849 |
getLabelText: function ( value ) { |
850 |
return String( value.length ); |
851 |
} |
852 |
} ), |
853 |
{ |
854 |
label: 'TextInputWidget (with dynamic label – length)\u200E', |
855 |
align: 'top' |
856 |
} |
857 |
), |
858 |
new OO.ui.FieldLayout( |
859 |
new Demo.DynamicLabelTextInputWidget( { |
860 |
maxLength: 300, |
861 |
getLabelText: function ( value ) { |
862 |
return String( 300 - value.length ); |
863 |
} |
864 |
} ), |
865 |
{ |
866 |
label: 'TextInputWidget (with dynamic label – remaining length)\u200E', |
867 |
align: 'top' |
868 |
} |
869 |
), |
870 |
new OO.ui.FieldLayout( |
871 |
new OO.ui.SearchInputWidget(), |
872 |
{ |
873 |
label: 'SearchInputWidget (type=search)\u200E', |
874 |
align: 'top' |
875 |
} |
876 |
), |
877 |
new OO.ui.FieldLayout( |
878 |
new OO.ui.SearchInputWidget( { disabled: true } ), |
879 |
{ |
880 |
label: 'SearchInputWidget (disabled)\u200E', |
881 |
align: 'top' |
882 |
} |
883 |
), |
884 |
new OO.ui.FieldLayout( |
885 |
new OO.ui.SearchInputWidget( { disabled: true, value: 'test' } ), |
886 |
{ |
887 |
label: 'SearchInputWidget (disabled, filled)\u200E', |
888 |
align: 'top' |
889 |
} |
890 |
), |
891 |
new OO.ui.FieldLayout( |
892 |
new OO.ui.NumberInputWidget(), |
893 |
{ |
894 |
label: 'NumberInputWidget', |
895 |
align: 'top' |
896 |
} |
897 |
), |
898 |
new OO.ui.FieldLayout( |
899 |
new OO.ui.NumberInputWidget( { disabled: true } ), |
900 |
{ |
901 |
label: 'NumberInputWidget (disabled)', |
902 |
align: 'top' |
903 |
} |
904 |
), |
905 |
new OO.ui.FieldLayout( |
906 |
new OO.ui.NumberInputWidget( { min: 1, max: 5, step: 1 } ), |
907 |
{ |
908 |
label: 'NumberInputWidget (1–5, ints only)', |
909 |
align: 'top' |
910 |
} |
911 |
), |
912 |
new OO.ui.FieldLayout( |
913 |
new OO.ui.NumberInputWidget( { min: -1, max: 1, step: 0.1, pageStep: 0.5 } ), |
914 |
{ |
915 |
label: 'NumberInputWidget (-1–1, step by .1, page by .5)', |
916 |
align: 'top' |
917 |
} |
918 |
), |
919 |
new OO.ui.FieldLayout( |
920 |
new OO.ui.NumberInputWidget( { showButtons: false } ), |
921 |
{ |
922 |
label: 'NumberInputWidget (no buttons)', |
923 |
align: 'top' |
924 |
} |
925 |
) |
926 |
] |
927 |
} ), |
928 |
new OO.ui.FieldsetLayout( { |
929 |
id: 'demo-section-inputs-binary', |
930 |
label: 'Checkbox, Radio & ToggleSwitch', |
931 |
items: [ |
932 |
new OO.ui.FieldLayout( |
933 |
new OO.ui.CheckboxInputWidget( { |
934 |
selected: true |
935 |
} ), |
936 |
{ |
937 |
align: 'inline', |
938 |
label: 'CheckboxInputWidget' |
939 |
} |
940 |
), |
941 |
new OO.ui.FieldLayout( |
942 |
new OO.ui.CheckboxInputWidget( { |
943 |
selected: true, |
944 |
disabled: true |
945 |
} ), |
946 |
{ |
947 |
align: 'inline', |
948 |
label: 'CheckboxInputWidget (disabled)\u200E' |
949 |
} |
950 |
), |
951 |
new OO.ui.FieldLayout( |
952 |
new OO.ui.CheckboxInputWidget( { |
953 |
selected: true, |
954 |
accessKey: 't' |
955 |
} ), |
956 |
{ |
957 |
align: 'inline', |
958 |
label: 'CheckboxInputWidget (with accesskey T and title)\u200E', |
959 |
title: 'Access key is added to the title.' |
960 |
} |
961 |
), |
962 |
new OO.ui.FieldLayout( |
963 |
new OO.ui.RadioInputWidget( { |
964 |
name: 'oojs-ui-radio-demo' |
965 |
} ), |
966 |
{ |
967 |
align: 'inline', |
968 |
label: 'Connected RadioInputWidget #1' |
969 |
} |
970 |
), |
971 |
new OO.ui.FieldLayout( |
972 |
new OO.ui.RadioInputWidget( { |
973 |
name: 'oojs-ui-radio-demo', |
974 |
selected: true |
975 |
} ), |
976 |
{ |
977 |
align: 'inline', |
978 |
label: 'Connected RadioInputWidget #2' |
979 |
} |
980 |
), |
981 |
new OO.ui.FieldLayout( |
982 |
new OO.ui.RadioInputWidget( { |
983 |
selected: true, |
984 |
disabled: true |
985 |
} ), |
986 |
{ |
987 |
align: 'inline', |
988 |
label: 'RadioInputWidget (disabled)\u200E' |
989 |
} |
990 |
), |
991 |
new OO.ui.FieldLayout( |
992 |
new OO.ui.RadioSelectWidget( { |
993 |
items: [ |
994 |
new OO.ui.RadioOptionWidget( { |
995 |
data: 'cat', |
996 |
label: 'Cat' |
997 |
} ), |
998 |
new OO.ui.RadioOptionWidget( { |
999 |
data: 'dog', |
1000 |
label: 'Dog' |
1001 |
} ), |
1002 |
new OO.ui.RadioOptionWidget( { |
1003 |
data: 'goldfish', |
1004 |
label: 'Goldfish. By the way, this is a very long label. ' + loremIpsum, |
1005 |
disabled: true |
1006 |
} ) |
1007 |
] |
1008 |
} ), |
1009 |
{ |
1010 |
align: 'top', |
1011 |
label: 'RadioSelectWidget' |
1012 |
} |
1013 |
), |
1014 |
new OO.ui.FieldLayout( |
1015 |
new OO.ui.CheckboxMultiselectWidget( { |
1016 |
items: [ |
1017 |
new OO.ui.CheckboxMultioptionWidget( { |
1018 |
data: 'cat', |
1019 |
label: 'Cat' |
1020 |
} ), |
1021 |
new OO.ui.CheckboxMultioptionWidget( { |
1022 |
data: 'dog', |
1023 |
label: 'Dog' |
1024 |
} ), |
1025 |
new OO.ui.CheckboxMultioptionWidget( { |
1026 |
data: 'goldfish', |
1027 |
label: 'Goldfish. By the way, this is a very long label. ' + loremIpsum, |
1028 |
disabled: true |
1029 |
} ) |
1030 |
] |
1031 |
} ), |
1032 |
{ |
1033 |
align: 'top', |
1034 |
label: 'CheckboxMultiselectWidget' |
1035 |
} |
1036 |
), |
1037 |
new OO.ui.FieldLayout( |
1038 |
new OO.ui.RadioSelectInputWidget( { |
1039 |
value: 'dog', |
1040 |
options: [ |
1041 |
{ |
1042 |
data: 'cat', |
1043 |
label: 'Cat' |
1044 |
}, |
1045 |
{ |
1046 |
data: 'dog', |
1047 |
label: 'Dog' |
1048 |
}, |
1049 |
{ |
1050 |
data: 'goldfish', |
1051 |
label: 'Goldfish' |
1052 |
} |
1053 |
] |
1054 |
} ), |
1055 |
{ |
1056 |
align: 'top', |
1057 |
label: 'RadioSelectInputWidget' |
1058 |
} |
1059 |
), |
1060 |
new OO.ui.FieldLayout( |
1061 |
new OO.ui.CheckboxMultiselectInputWidget( { |
1062 |
value: [ 'dog', 'cat' ], |
1063 |
options: [ |
1064 |
{ |
1065 |
data: 'cat', |
1066 |
label: 'Cat' |
1067 |
}, |
1068 |
{ |
1069 |
data: 'dog', |
1070 |
label: 'Dog (disabled)\u200E', |
1071 |
disabled: true |
1072 |
}, |
1073 |
{ |
1074 |
data: 'goldfish', |
1075 |
label: 'Goldfish' |
1076 |
} |
1077 |
] |
1078 |
} ), |
1079 |
{ |
1080 |
align: 'top', |
1081 |
label: 'CheckboxMultiselectInputWidget' |
1082 |
} |
1083 |
), |
1084 |
new OO.ui.FieldLayout( |
1085 |
new OO.ui.RadioSelectWidget( { |
1086 |
items: [ |
1087 |
new OO.ui.RadioOptionWidget( { |
1088 |
data: 'a', |
1089 |
label: 'Access key: M', |
1090 |
accessKey: 'm' |
1091 |
} ), |
1092 |
new OO.ui.RadioOptionWidget( { |
1093 |
data: 'b', |
1094 |
label: 'Access key: N', |
1095 |
accessKey: 'n' |
1096 |
} ), |
1097 |
new OO.ui.RadioOptionWidget( { |
1098 |
data: 'c', |
1099 |
label: 'Access key: O', |
1100 |
accessKey: 'o' |
1101 |
} ) |
1102 |
] |
1103 |
} ), |
1104 |
{ |
1105 |
align: 'top', |
1106 |
label: 'RadioSelectWidget (with accesskeys)\u200E' |
1107 |
} |
1108 |
), |
1109 |
new OO.ui.FieldLayout( |
1110 |
new OO.ui.ToggleSwitchWidget(), |
1111 |
{ |
1112 |
label: 'ToggleSwitchWidget', |
1113 |
align: 'top' |
1114 |
} |
1115 |
), |
1116 |
new OO.ui.FieldLayout( |
1117 |
new OO.ui.ToggleSwitchWidget( { disabled: true } ), |
1118 |
{ |
1119 |
label: 'ToggleSwitchWidget (disabled)\u200E', |
1120 |
align: 'top' |
1121 |
} |
1122 |
), |
1123 |
new OO.ui.FieldLayout( |
1124 |
new OO.ui.ToggleSwitchWidget( { disabled: true, value: true } ), |
1125 |
{ |
1126 |
label: 'ToggleSwitchWidget (disabled, checked)\u200E', |
1127 |
align: 'top' |
1128 |
} |
1129 |
), |
1130 |
new OO.ui.FieldLayout( |
1131 |
new OO.ui.RadioSelectWidget( { |
1132 |
items: [ |
1133 |
new OO.ui.RadioOptionWidget( { |
1134 |
data: 'a', |
Error |
Row 1135, Column 19: "Prefer createHTMLDocument to $.parseHTML"
jquery/no-parse-html
|
1135 |
label: $( $.parseHTML( 'Option A (<a href="https://example.com/a">details</a>)' ) ) |
1136 |
} ), |
1137 |
new OO.ui.RadioOptionWidget( { |
1138 |
data: 'b', |
Error |
Row 1139, Column 19: "Prefer createHTMLDocument to $.parseHTML"
jquery/no-parse-html
|
1139 |
label: $( $.parseHTML( 'Option B (<a href="https://example.com/b">details</a>)' ) ) |
1140 |
} ), |
1141 |
new OO.ui.RadioOptionWidget( { |
1142 |
data: 'c', |
Error |
Row 1143, Column 19: "Prefer createHTMLDocument to $.parseHTML"
jquery/no-parse-html
|
1143 |
label: $( $.parseHTML( 'Option C (<a href="https://example.com/c">details</a>)' ) ) |
1144 |
} ) |
1145 |
] |
1146 |
} ), |
1147 |
{ |
1148 |
label: 'RadioSelectWidget with links in the labels', |
1149 |
align: 'top' |
1150 |
} |
1151 |
), |
1152 |
new OO.ui.FieldLayout( |
1153 |
new OO.ui.RadioSelectWidget( { |
1154 |
items: [ |
1155 |
new OO.ui.RadioOptionWidget( { |
1156 |
data: 'foo', |
1157 |
label: 'Foo' |
1158 |
} ), |
1159 |
new OO.ui.RadioOptionWidget( { |
1160 |
data: 'bar', |
1161 |
label: 'Bar' |
1162 |
} ), |
1163 |
new OO.ui.RadioOptionWidget( { |
1164 |
data: '', |
1165 |
label: $( [ |
1166 |
document.createTextNode( 'Other: ' ), |
1167 |
new OO.ui.TextInputWidget().$element[ 0 ] |
1168 |
] ) |
1169 |
} ) |
1170 |
] |
1171 |
} ), |
1172 |
{ |
1173 |
label: 'RadioSelectWidget with text input in a label', |
1174 |
align: 'top' |
1175 |
} |
1176 |
) |
1177 |
] |
1178 |
} ), |
1179 |
new OO.ui.FieldsetLayout( { |
1180 |
id: 'demo-section-dropdown', |
1181 |
label: 'Dropdown', |
1182 |
items: [ |
1183 |
new OO.ui.FieldLayout( |
1184 |
new OO.ui.DropdownWidget( { |
1185 |
label: 'Select one', |
1186 |
menu: { |
1187 |
items: [ |
1188 |
new OO.ui.MenuOptionWidget( { |
1189 |
data: 'a', |
1190 |
label: 'First' |
1191 |
} ), |
1192 |
new OO.ui.MenuOptionWidget( { |
1193 |
data: 'b', |
1194 |
label: 'Second', |
1195 |
indicator: 'clear' |
1196 |
} ), |
1197 |
new OO.ui.MenuOptionWidget( { |
1198 |
data: 'c', |
1199 |
label: 'Third' |
1200 |
} ), |
1201 |
new OO.ui.MenuOptionWidget( { |
1202 |
data: 'c', |
1203 |
label: 'The fourth option has an overly long label' |
1204 |
} ), |
1205 |
new OO.ui.MenuOptionWidget( { |
1206 |
icon: 'feedback', |
1207 |
data: 'd', |
1208 |
label: 'The fifth option has an icon' |
1209 |
} ) |
1210 |
] |
1211 |
} |
1212 |
} ), |
1213 |
{ |
1214 |
label: 'DropdownWidget', |
1215 |
align: 'top' |
1216 |
} |
1217 |
), |
1218 |
new OO.ui.FieldLayout( |
1219 |
new OO.ui.DropdownWidget( { |
1220 |
label: 'Select one', |
1221 |
icon: 'tag', |
1222 |
menu: { |
1223 |
items: [ |
1224 |
new OO.ui.MenuOptionWidget( { |
1225 |
data: 'a', |
1226 |
label: 'First' |
1227 |
} ), |
1228 |
new OO.ui.MenuOptionWidget( { |
1229 |
data: 'b', |
1230 |
label: 'Disabled second option', |
1231 |
indicator: 'clear', |
1232 |
disabled: true |
1233 |
} ), |
1234 |
new OO.ui.MenuOptionWidget( { |
1235 |
data: 'c', |
1236 |
label: 'Third' |
1237 |
} ), |
1238 |
new OO.ui.MenuOptionWidget( { |
1239 |
data: 'd', |
1240 |
label: 'Disabled fourth option with an overly long label', |
1241 |
disabled: true |
1242 |
} ), |
1243 |
new OO.ui.MenuOptionWidget( { |
1244 |
data: 'c', |
1245 |
label: 'Third' |
1246 |
} ) |
1247 |
] |
1248 |
} |
1249 |
} ), |
1250 |
{ |
1251 |
label: 'DropdownWidget (disabled options)\u200E', |
1252 |
align: 'top' |
1253 |
} |
1254 |
), |
1255 |
new OO.ui.FieldLayout( |
1256 |
new OO.ui.DropdownWidget( { |
1257 |
label: 'Select one', |
1258 |
menu: { |
1259 |
items: [ |
1260 |
new OO.ui.MenuSectionOptionWidget( { |
1261 |
label: 'Dogs' |
1262 |
} ), |
1263 |
new OO.ui.MenuOptionWidget( { |
1264 |
data: 'corgi', |
1265 |
label: 'Welsh Corgi', |
1266 |
indicator: 'required' |
1267 |
} ), |
1268 |
new OO.ui.MenuOptionWidget( { |
1269 |
data: 'poodle', |
1270 |
label: 'Standard Poodle', |
1271 |
icon: 'star' |
1272 |
} ), |
1273 |
new OO.ui.MenuSectionOptionWidget( { |
1274 |
label: 'Cats' |
1275 |
} ), |
1276 |
new OO.ui.MenuOptionWidget( { |
1277 |
data: 'lion', |
1278 |
label: 'Lion' |
1279 |
} ) |
1280 |
] |
1281 |
} |
1282 |
} ), |
1283 |
{ |
1284 |
label: 'DropdownWidget (with MenuSectionOptionWidget)\u200E', |
1285 |
align: 'top' |
1286 |
} |
1287 |
), |
1288 |
new OO.ui.FieldLayout( |
1289 |
new OO.ui.DropdownWidget( { |
1290 |
label: 'Select one', |
1291 |
disabled: true, |
1292 |
menu: { |
1293 |
items: [ |
1294 |
new OO.ui.MenuOptionWidget( { |
1295 |
data: 'a', |
1296 |
label: 'First' |
1297 |
} ), |
1298 |
new OO.ui.MenuOptionWidget( { |
1299 |
data: 'b', |
1300 |
label: 'Second' |
1301 |
} ), |
1302 |
new OO.ui.MenuOptionWidget( { |
1303 |
data: 'c', |
1304 |
label: 'Third' |
1305 |
} ), |
1306 |
new OO.ui.MenuOptionWidget( { |
1307 |
data: 'd', |
1308 |
label: 'Fourth' |
1309 |
} ) |
1310 |
] |
1311 |
} |
1312 |
} ), |
1313 |
{ |
1314 |
label: 'DropdownWidget (disabled)\u200E', |
1315 |
align: 'top' |
1316 |
} |
1317 |
), |
1318 |
new OO.ui.FieldLayout( |
1319 |
new OO.ui.DropdownWidget( { |
1320 |
label: 'Select one', |
1321 |
$overlay: true, |
1322 |
menu: { |
1323 |
items: [ |
1324 |
new OO.ui.MenuOptionWidget( { |
1325 |
data: 'a', |
1326 |
label: 'First' |
1327 |
} ), |
1328 |
new OO.ui.MenuOptionWidget( { |
1329 |
data: 'b', |
1330 |
label: 'Second' |
1331 |
} ), |
1332 |
new OO.ui.MenuOptionWidget( { |
1333 |
data: 'c', |
1334 |
label: 'Third' |
1335 |
} ), |
1336 |
new OO.ui.MenuOptionWidget( { |
1337 |
data: 'd', |
1338 |
label: 'Fourth' |
1339 |
} ) |
1340 |
] |
1341 |
} |
1342 |
} ), |
1343 |
{ |
1344 |
label: 'DropdownWidget (using default overlay)\u200E', |
1345 |
align: 'top' |
1346 |
} |
1347 |
), |
1348 |
new OO.ui.FieldLayout( |
1349 |
new OO.ui.DropdownWidget( { |
1350 |
label: 'Select one', |
1351 |
$overlay: $overlay, |
1352 |
menu: { |
1353 |
items: [ |
1354 |
new OO.ui.MenuOptionWidget( { |
1355 |
data: 'a', |
1356 |
label: 'First' |
1357 |
} ), |
1358 |
new OO.ui.MenuOptionWidget( { |
1359 |
data: 'b', |
1360 |
label: 'Second' |
1361 |
} ), |
1362 |
new OO.ui.MenuOptionWidget( { |
1363 |
data: 'c', |
1364 |
label: 'Third' |
1365 |
} ), |
1366 |
new OO.ui.MenuOptionWidget( { |
1367 |
data: 'd', |
1368 |
label: 'Fourth' |
1369 |
} ) |
1370 |
] |
1371 |
} |
1372 |
} ), |
1373 |
{ |
1374 |
label: 'DropdownWidget (using custom overlay)\u200E', |
1375 |
align: 'top' |
1376 |
} |
1377 |
), |
1378 |
new OO.ui.FieldLayout( |
1379 |
new OO.ui.DropdownWidget( { |
1380 |
label: 'Select one', |
1381 |
menu: { |
1382 |
items: [ |
1383 |
new OO.ui.MenuOptionWidget( { |
1384 |
data: 'a', |
1385 |
label: 'First' |
1386 |
} ), |
1387 |
new OO.ui.MenuOptionWidget( { |
1388 |
data: 'b', |
1389 |
label: 'Second' |
1390 |
} ), |
1391 |
new OO.ui.MenuOptionWidget( { |
1392 |
data: 'c', |
1393 |
label: 'Third' |
1394 |
} ), |
1395 |
new OO.ui.MenuOptionWidget( { |
1396 |
data: 'd', |
1397 |
label: 'Fourth' |
1398 |
} ) |
1399 |
], |
1400 |
hideOnChoose: false |
1401 |
} |
1402 |
} ), |
1403 |
{ |
1404 |
label: 'DropdownWidget (does not close on choose)\u200E', |
1405 |
align: 'top' |
1406 |
} |
1407 |
), |
1408 |
new OO.ui.FieldLayout( |
1409 |
new OO.ui.DropdownWidget( { |
1410 |
menu: { |
1411 |
items: [ |
1412 |
new OO.ui.MenuOptionWidget( { |
1413 |
data: 'a', |
1414 |
label: 'Access key: P', |
1415 |
accessKey: 'p' |
1416 |
} ), |
1417 |
new OO.ui.MenuOptionWidget( { |
1418 |
data: 'b', |
1419 |
label: 'Access key: Q', |
1420 |
accessKey: 'q' |
1421 |
} ), |
1422 |
new OO.ui.MenuOptionWidget( { |
1423 |
data: 'c', |
1424 |
label: 'Access key: R', |
1425 |
accessKey: 'r' |
1426 |
} ) |
1427 |
] |
1428 |
} |
1429 |
} ), |
1430 |
{ |
1431 |
align: 'top', |
1432 |
label: 'DropdownWidget (with accesskeys)\u200E' |
1433 |
} |
1434 |
), |
1435 |
new OO.ui.FieldLayout( |
1436 |
new OO.ui.DropdownInputWidget( { |
1437 |
options: [ |
1438 |
{ |
1439 |
data: 'a', |
1440 |
label: 'First' |
1441 |
}, |
1442 |
{ |
1443 |
data: 'b', |
1444 |
label: 'Second' |
1445 |
}, |
1446 |
{ |
1447 |
data: 'c', |
1448 |
label: 'Third' |
1449 |
} |
1450 |
], |
1451 |
value: 'b' |
1452 |
} ), |
1453 |
{ |
1454 |
label: 'DropdownInputWidget', |
1455 |
align: 'top' |
1456 |
} |
1457 |
), |
1458 |
new OO.ui.FieldLayout( |
1459 |
new OO.ui.DropdownInputWidget( { |
1460 |
disabled: true |
1461 |
} ), |
1462 |
{ |
1463 |
label: 'DropdownInputWidget (disabled)', |
1464 |
align: 'top' |
1465 |
} |
1466 |
), |
1467 |
new OO.ui.FieldLayout( |
1468 |
new OO.ui.DropdownInputWidget( { |
1469 |
options: [ |
1470 |
{ |
1471 |
optgroup: 'Vowels' |
1472 |
}, |
1473 |
{ |
1474 |
data: 'a', |
1475 |
label: 'A' |
1476 |
}, |
1477 |
{ |
1478 |
optgroup: 'Consonants' |
1479 |
}, |
1480 |
{ |
1481 |
data: 'b', |
1482 |
label: 'B' |
1483 |
}, |
1484 |
{ |
1485 |
data: 'c', |
1486 |
label: 'C' |
1487 |
} |
1488 |
], |
1489 |
value: 'b' |
1490 |
} ), |
1491 |
{ |
1492 |
label: 'DropdownInputWidget (with optgroup)', |
1493 |
align: 'top' |
1494 |
} |
1495 |
), |
1496 |
new OO.ui.FieldLayout( |
1497 |
new OO.ui.DropdownInputWidget( { |
1498 |
options: [ |
1499 |
{ data: 'sq', label: 'Albanian' }, |
1500 |
{ data: 'frp', label: 'Arpitan' }, |
1501 |
{ data: 'ba', label: 'Bashkir' }, |
1502 |
{ data: 'pt-br', label: 'Brazilian Portuguese' }, |
1503 |
{ data: 'tzm', label: 'Central Atlas Tamazight' }, |
1504 |
{ data: 'zh', label: 'Chinese' }, |
1505 |
{ data: 'co', label: 'Corsican' }, |
1506 |
{ data: 'del', label: 'Delaware' }, |
1507 |
{ data: 'eml', label: 'Emiliano-Romagnolo' }, |
1508 |
{ data: 'en', label: 'English' }, |
1509 |
{ data: 'fi', label: 'Finnish' }, |
1510 |
{ data: 'aln', label: 'Gheg Albanian' }, |
1511 |
{ data: 'he', label: 'Hebrew' }, |
1512 |
{ data: 'ilo', label: 'Iloko' }, |
1513 |
{ data: 'kbd', label: 'Kabardian' }, |
1514 |
{ data: 'csb', label: 'Kashubian' }, |
1515 |
{ data: 'avk', label: 'Kotava' }, |
1516 |
{ data: 'lez', label: 'Lezghian' }, |
1517 |
{ data: 'nds-nl', label: 'Low Saxon' }, |
1518 |
{ data: 'ml', label: 'Malayalam' }, |
1519 |
{ data: 'dum', label: 'Middle Dutch' }, |
1520 |
{ data: 'ary', label: 'Moroccan Arabic' }, |
1521 |
{ data: 'pih', label: 'Norfuk / Pitkern' }, |
1522 |
{ data: 'ny', label: 'Nyanja' }, |
1523 |
{ data: 'ang', label: 'Old English' }, |
1524 |
{ data: 'non', label: 'Old Norse' }, |
1525 |
{ data: 'pau', label: 'Palauan' }, |
1526 |
{ data: 'pdt', label: 'Plautdietsch' }, |
1527 |
{ data: 'ru', label: 'Russian' }, |
1528 |
{ data: 'stq', label: 'Saterland Frisian' }, |
1529 |
{ data: 'ii', label: 'Sichuan Yi' }, |
1530 |
{ data: 'bcc', label: 'Southern Balochi' }, |
1531 |
{ data: 'shi', label: 'Tachelhit' }, |
1532 |
{ data: 'th', label: 'Thai' }, |
1533 |
{ data: 'tr', label: 'Turkish' }, |
1534 |
{ data: 'fiu-vro', label: 'Võro' }, |
1535 |
{ data: 'vls', label: 'West Flemish' }, |
1536 |
{ data: 'zea', label: 'Zeelandic' } |
1537 |
], |
1538 |
value: 'en' |
1539 |
} ), |
1540 |
{ |
1541 |
label: 'DropdownInputWidget (long)\u200E', |
1542 |
align: 'top' |
1543 |
} |
1544 |
) |
1545 |
] |
1546 |
} ), |
1547 |
new OO.ui.FieldsetLayout( { |
1548 |
id: 'demo-section-comboBox', |
1549 |
label: 'ComboBox', |
1550 |
items: [ |
1551 |
new OO.ui.FieldLayout( |
1552 |
new OO.ui.ComboBoxInputWidget( { |
1553 |
options: [ |
1554 |
{ data: 'asd', label: 'Label for asd' }, |
1555 |
{ data: 'fgh', label: 'Label for fgh' }, |
1556 |
{ data: 'jkl', label: 'Label for jkl' }, |
1557 |
{ data: 'zxc', label: 'Label for zxc' }, |
1558 |
{ data: 'vbn', label: 'Label for vbn' } |
1559 |
] |
1560 |
} ), |
1561 |
{ |
1562 |
label: 'ComboBoxInputWidget', |
1563 |
align: 'top' |
1564 |
} |
1565 |
), |
1566 |
new OO.ui.FieldLayout( |
1567 |
new OO.ui.ComboBoxInputWidget( { |
1568 |
options: [ |
1569 |
{ data: 'asd', label: 'A Label for asd' }, |
1570 |
{ data: 'fgh', label: 'A Label for fgh' }, |
1571 |
{ data: 'jkl', label: 'A Label for jkl' }, |
1572 |
{ data: 'zxc', label: 'A Label for zxc' }, |
1573 |
{ data: 'vbn', label: 'A Label for vbn' }, |
1574 |
{ data: 'asd', label: 'B Label for asd' }, |
1575 |
{ data: 'fgh', label: 'B Label for fgh' }, |
1576 |
{ data: 'jkl', label: 'B Label for jkl' }, |
1577 |
{ data: 'zxc', label: 'B Label for zxc' }, |
1578 |
{ data: 'vbn', label: 'B Label for vbn' }, |
1579 |
{ data: 'asd', label: 'C Label for asd' }, |
1580 |
{ data: 'fgh', label: 'C Label for fgh' }, |
1581 |
{ data: 'jkl', label: 'C Label for jkl' }, |
1582 |
{ data: 'zxc', label: 'C Label for zxc' }, |
1583 |
{ data: 'vbn', label: 'C Label for vbn' }, |
1584 |
{ data: 'asd', label: 'D Label for asd' }, |
1585 |
{ data: 'fgh', label: 'D Label for fgh' }, |
1586 |
{ data: 'jkl', label: 'D Label for jkl' }, |
1587 |
{ data: 'zxc', label: 'D Label for zxc' }, |
1588 |
{ data: 'vbn', label: 'D Label for vbn' }, |
1589 |
{ data: 'asd', label: 'E Label for asd' }, |
1590 |
{ data: 'fgh', label: 'E Label for fgh' }, |
1591 |
{ data: 'jkl', label: 'E Label for jkl' }, |
1592 |
{ data: 'zxc', label: 'E Label for zxc' }, |
1593 |
{ data: 'vbn', label: 'E Label for vbn' } |
1594 |
], |
1595 |
menu: { |
1596 |
filterFromInput: true |
1597 |
} |
1598 |
} ), |
1599 |
{ |
1600 |
label: 'ComboBoxInputWidget (filtering on input)', |
1601 |
align: 'top' |
1602 |
} |
1603 |
), |
1604 |
new OO.ui.FieldLayout( |
1605 |
new OO.ui.ComboBoxInputWidget( { |
1606 |
options: [ |
1607 |
{ data: 'Option 1' }, |
1608 |
{ data: 'Option 2' }, |
1609 |
{ data: 'Option 3' }, |
1610 |
{ data: 'Option 4' }, |
1611 |
{ data: 'Option 5' } |
1612 |
] |
1613 |
} ), |
1614 |
{ |
1615 |
label: 'ComboBoxInputWidget (no labels given)', |
1616 |
align: 'top' |
1617 |
} |
1618 |
), |
1619 |
new OO.ui.FieldLayout( |
1620 |
new OO.ui.ComboBoxInputWidget( { |
1621 |
disabled: true, |
1622 |
options: [ |
1623 |
{ data: 'asd', label: 'Label for asd' }, |
1624 |
{ data: 'fgh', label: 'Label for fgh' }, |
1625 |
{ data: 'jkl', label: 'Label for jkl' }, |
1626 |
{ data: 'zxc', label: 'Label for zxc' }, |
1627 |
{ data: 'vbn', label: 'Label for vbn' } |
1628 |
] |
1629 |
} ), |
1630 |
{ |
1631 |
label: 'ComboBoxInputWidget (disabled)\u200E', |
1632 |
align: 'top' |
1633 |
} |
1634 |
), |
1635 |
new OO.ui.FieldLayout( |
1636 |
new OO.ui.ComboBoxInputWidget(), |
1637 |
{ |
1638 |
label: 'ComboBoxInputWidget (empty)\u200E', |
1639 |
align: 'top' |
1640 |
} |
1641 |
) |
1642 |
] |
1643 |
} ), |
1644 |
new OO.ui.FieldsetLayout( { |
1645 |
id: 'demo-section-selectFile', |
1646 |
label: 'SelectFile', |
1647 |
items: [ |
1648 |
new OO.ui.FieldLayout( |
1649 |
new OO.ui.SelectFileWidget( {} ), |
1650 |
{ |
1651 |
label: 'SelectFileWidget\u200E', |
1652 |
align: 'top' |
1653 |
} |
1654 |
), |
1655 |
new OO.ui.FieldLayout( |
1656 |
new OO.ui.SelectFileWidget( { accept: [ 'image/png', 'image/jpeg' ] } ), |
1657 |
{ |
1658 |
label: 'SelectFileWidget (accept PNG and JPEG)\u200E', |
1659 |
align: 'top' |
1660 |
} |
1661 |
), |
1662 |
new OO.ui.FieldLayout( |
1663 |
new OO.ui.SelectFileWidget( { |
1664 |
icon: 'tag', |
1665 |
indicator: 'clear' |
1666 |
} ), |
1667 |
{ |
1668 |
label: 'SelectFileWidget (icon, indicator)\u200E', |
1669 |
align: 'top' |
1670 |
} |
1671 |
), |
1672 |
new OO.ui.FieldLayout( |
1673 |
new OO.ui.SelectFileWidget( { |
1674 |
icon: 'tag', |
1675 |
indicator: 'clear', |
1676 |
disabled: true |
1677 |
} ), |
1678 |
{ |
1679 |
label: 'SelectFileWidget (disabled)\u200E', |
1680 |
align: 'top' |
1681 |
} |
1682 |
), |
1683 |
new OO.ui.FieldLayout( |
1684 |
new Demo.UnsupportedSelectFileWidget(), |
1685 |
{ |
1686 |
label: 'SelectFileWidget (no browser support)\u200E', |
1687 |
align: 'top' |
1688 |
} |
1689 |
), |
1690 |
new OO.ui.FieldLayout( |
1691 |
new OO.ui.SelectFileWidget( { showDropTarget: true } ), |
1692 |
{ |
1693 |
label: 'SelectFileWidget (with drop target)\u200E', |
1694 |
align: 'top' |
1695 |
} |
1696 |
), |
1697 |
new OO.ui.FieldLayout( |
1698 |
new OO.ui.SelectFileWidget( { |
1699 |
showDropTarget: true, |
1700 |
disabled: true |
1701 |
} ), |
1702 |
{ |
1703 |
label: 'SelectFileWidget (with drop target, disabled)\u200E', |
1704 |
align: 'top' |
1705 |
} |
1706 |
), |
1707 |
new OO.ui.FieldLayout( |
1708 |
new Demo.UnsupportedSelectFileWidget( { |
1709 |
showDropTarget: true |
1710 |
} ), |
1711 |
{ |
1712 |
label: 'SelectFileWidget (with drop target, no browser support)\u200E', |
1713 |
align: 'top' |
1714 |
} |
1715 |
) |
1716 |
] |
1717 |
} ), |
1718 |
new OO.ui.FieldsetLayout( { |
1719 |
id: 'demo-section-tagMultiselect', |
1720 |
label: 'TagMultiselect, MenuTagMultiselect', |
1721 |
items: [ |
1722 |
new OO.ui.FieldLayout( |
1723 |
new OO.ui.TagMultiselectWidget( { |
1724 |
placeholder: 'Add tags', |
1725 |
allowArbitrary: true |
1726 |
} ), |
1727 |
{ |
1728 |
label: 'TagMultiselectWidget (allowArbitrary, inline input, placeholder)', |
1729 |
align: 'top' |
1730 |
} |
1731 |
), |
1732 |
new OO.ui.FieldLayout( |
1733 |
new OO.ui.TagMultiselectWidget( { |
1734 |
selected: [ loremIpsum ], |
1735 |
allowArbitrary: true |
1736 |
} ), |
1737 |
{ |
1738 |
label: 'TagMultiselectWidget (very long item)', |
1739 |
align: 'top' |
1740 |
} |
1741 |
), |
1742 |
new OO.ui.FieldLayout( |
1743 |
fixedItemsTagMultiselectWidget, |
1744 |
{ |
1745 |
label: 'TagMultiselectWidget with fixed items', |
1746 |
align: 'top' |
1747 |
} |
1748 |
), |
1749 |
new OO.ui.FieldLayout( |
1750 |
new OO.ui.TagMultiselectWidget( { |
1751 |
placeholder: 'Add tags', |
1752 |
allowArbitrary: true, |
1753 |
disabled: true |
1754 |
} ), |
1755 |
{ |
1756 |
label: 'TagMultiselectWidget (disabled, inline input, placeholder)', |
1757 |
align: 'top' |
1758 |
} |
1759 |
), |
1760 |
new OO.ui.FieldLayout( |
1761 |
new OO.ui.TagMultiselectWidget( { |
1762 |
allowArbitrary: false, |
1763 |
allowDisplayInvalidTags: true, |
1764 |
allowedValues: [ 'foo', 'bar', 'baz' ] |
1765 |
} ), |
1766 |
{ |
1767 |
label: 'TagMultiselectWidget (inline input, allowed values: [ \'foo\', \'bar\', \'baz\' ], allowDisplayInvalidTags)', |
1768 |
align: 'top' |
1769 |
} |
1770 |
), |
1771 |
new OO.ui.FieldLayout( |
1772 |
new OO.ui.TagMultiselectWidget( { |
1773 |
placeholder: 'Add tags', |
1774 |
allowArbitrary: true, |
1775 |
inputPosition: 'outline' |
1776 |
} ), |
1777 |
{ |
1778 |
label: 'TagMultiselectWidget (allowArbitrary, inputPosition:outline, placeholder)', |
1779 |
align: 'top' |
1780 |
} |
1781 |
), |
1782 |
new OO.ui.FieldLayout( |
1783 |
new OO.ui.TagMultiselectWidget( { |
1784 |
allowArbitrary: true, |
1785 |
inputPosition: 'outline', |
1786 |
inputWidget: new OO.ui.NumberInputWidget() |
1787 |
} ), |
1788 |
{ |
1789 |
label: 'TagMultiselectWidget (inputwidget: OO.ui.NumberInputWidget, inputPosition:outline)', |
1790 |
align: 'top' |
1791 |
} |
1792 |
), |
1793 |
new OO.ui.FieldLayout( |
1794 |
new OO.ui.PopupTagMultiselectWidget( { |
1795 |
allowArbitrary: true, |
1796 |
icon: 'tag', |
1797 |
indicator: 'clear' |
1798 |
} ), |
1799 |
{ |
1800 |
label: 'PopupTagMultiselectWidget (icon, indicator, allowArbitrary)', |
1801 |
align: 'top' |
1802 |
} |
1803 |
), |
1804 |
new OO.ui.FieldLayout( |
1805 |
new OO.ui.MenuTagMultiselectWidget( { |
1806 |
selected: [ |
1807 |
{ data: 'foo', label: 'Label for foo' }, |
1808 |
{ data: 'bar', label: 'Label for bar' } |
1809 |
], |
1810 |
options: [ |
1811 |
{ data: 'foo', label: 'Label for foo', icon: 'tag' }, |
1812 |
{ data: 'bar', label: 'Label for bar' }, |
1813 |
{ data: 'baz', label: 'Label for baz' } |
1814 |
] |
1815 |
} ), |
1816 |
{ |
1817 |
label: 'MenuTagMultiselectWidget (initially selected, preset options)', |
1818 |
align: 'top' |
1819 |
} |
1820 |
), |
1821 |
new OO.ui.FieldLayout( |
1822 |
new OO.ui.MenuTagMultiselectWidget( { |
1823 |
selected: [ 'foo', 'bar' ], |
1824 |
allowArbitrary: true |
1825 |
} ), |
1826 |
{ |
1827 |
label: 'MenuTagMultiselectWidget (initially selected, allowArbitrary)', |
1828 |
align: 'top' |
1829 |
} |
1830 |
), |
1831 |
new OO.ui.FieldLayout( |
1832 |
new OO.ui.MenuTagMultiselectWidget( { |
1833 |
allowArbitrary: false, |
1834 |
options: [ |
1835 |
{ data: 'abc', label: 'Label for abc' }, |
1836 |
{ data: 'asd', label: 'Label for asd' }, |
1837 |
{ data: 'jkl', label: 'Label for jkl' } |
1838 |
] |
1839 |
} ), |
1840 |
{ |
1841 |
label: 'MenuTagMultiselectWidget (allowArbitrary:false)', |
1842 |
align: 'top' |
1843 |
} |
1844 |
), |
1845 |
new OO.ui.FieldLayout( |
1846 |
new OO.ui.MenuTagMultiselectWidget( { |
1847 |
allowArbitrary: false, |
1848 |
menu: { |
1849 |
highlightOnFilter: true |
1850 |
}, |
1851 |
options: [ |
1852 |
{ data: 'abc', label: 'abc item' }, |
1853 |
{ data: 'asd', label: 'asd item' }, |
1854 |
{ data: 'jkl', label: 'jkl item' }, |
1855 |
{ data: 'jkl2', label: 'jkl second item' }, |
1856 |
{ data: 'jkl3', label: 'jkl third item' } |
1857 |
] |
1858 |
} ), |
1859 |
{ |
1860 |
label: 'MenuTagMultiselectWidget (allowArbitrary:false, menu:{highlightOnFilter:true})', |
1861 |
align: 'top' |
1862 |
} |
1863 |
), |
1864 |
new OO.ui.FieldLayout( |
1865 |
new OO.ui.MenuTagMultiselectWidget( { |
1866 |
inputPosition: 'outline', |
1867 |
allowArbitrary: false, |
1868 |
options: [ |
1869 |
{ data: 'abc', label: 'Label for abc' }, |
1870 |
{ data: 'asd', label: 'Label for asd' }, |
1871 |
{ data: 'jkl', label: 'Label for jkl' } |
1872 |
] |
1873 |
} ), |
1874 |
{ |
1875 |
label: 'MenuTagMultiselectWidget (inputPosition:outline, allowArbitrary:false)', |
1876 |
align: 'top' |
1877 |
} |
1878 |
), |
1879 |
new OO.ui.FieldLayout( |
1880 |
new OO.ui.MenuTagMultiselectWidget( { |
1881 |
inputPosition: 'inline', |
1882 |
disabled: true, |
1883 |
options: [ |
1884 |
{ data: 'abc', label: 'Label for abc' }, |
1885 |
{ data: 'asd', label: 'Label for asd' }, |
1886 |
{ data: 'jkl', label: 'Label for jkl' } |
1887 |
] |
1888 |
} ), |
1889 |
{ |
1890 |
label: 'MenuTagMultiselectWidget (disabled)', |
1891 |
align: 'top' |
1892 |
} |
1893 |
) |
1894 |
] |
1895 |
} ), |
1896 |
new OO.ui.FieldsetLayout( { |
1897 |
id: 'demo-section-lookupElement', |
1898 |
label: 'LookupElement', |
1899 |
items: [ |
1900 |
new OO.ui.FieldLayout( |
1901 |
new Demo.NumberLookupTextInputWidget(), |
1902 |
{ |
1903 |
label: 'LookupElement (try inputting an integer)\u200E', |
1904 |
align: 'top' |
1905 |
} |
1906 |
), |
1907 |
new OO.ui.FieldLayout( |
1908 |
new Demo.NumberLookupTextInputWidget( { |
1909 |
highlightFirst: false |
1910 |
} ), |
1911 |
{ |
1912 |
label: 'LookupElement without highlighting 1st term (try inputting an integer)\u200E', |
1913 |
align: 'top' |
1914 |
} |
1915 |
) |
1916 |
] |
1917 |
} ), |
1918 |
new OO.ui.FieldsetLayout( { |
1919 |
id: 'demo-section-popupButton', |
1920 |
label: 'PopupButton', |
1921 |
items: [ |
1922 |
new OO.ui.FieldLayout( |
1923 |
new OO.ui.PopupButtonWidget( { |
1924 |
icon: 'info', |
1925 |
framed: false, |
1926 |
popup: { |
1927 |
head: true, |
1928 |
label: 'More information', |
Error |
Row 1929, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
1929 |
$content: $( '<p>' ).text( loremIpsum ), |
1930 |
padded: true, |
1931 |
align: 'force-left' |
1932 |
} |
1933 |
} ), |
1934 |
{ |
1935 |
label: 'PopupButtonWidget (frameless, with popup head, align: force-left)\u200E', |
1936 |
align: 'top' |
1937 |
} |
1938 |
), |
1939 |
new OO.ui.FieldLayout( |
1940 |
new OO.ui.PopupButtonWidget( { |
1941 |
icon: 'info', |
1942 |
framed: false, |
1943 |
popup: { |
1944 |
head: true, |
1945 |
label: 'More information', |
Error |
Row 1946, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
1946 |
$content: $( '<p>' ).text( loremIpsum ), |
1947 |
padded: true, |
1948 |
align: 'force-right' |
1949 |
} |
1950 |
} ), |
1951 |
{ |
1952 |
label: 'PopupButtonWidget (frameless, with popup head align: force-right)\u200E', |
1953 |
align: 'top' |
1954 |
} |
1955 |
), |
1956 |
new OO.ui.FieldLayout( |
1957 |
new OO.ui.PopupButtonWidget( { |
1958 |
icon: 'info', |
1959 |
framed: false, |
1960 |
popup: { |
1961 |
head: true, |
1962 |
label: 'More information', |
Error |
Row 1963, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
1963 |
$content: $( '<p>' ).text( loremIpsum ), |
1964 |
padded: true, |
1965 |
align: 'backwards' |
1966 |
} |
1967 |
} ), |
1968 |
{ |
1969 |
label: 'PopupButtonWidget (frameless, with popup head align: backwards)\u200E', |
1970 |
align: 'top' |
1971 |
} |
1972 |
), |
1973 |
new OO.ui.FieldLayout( |
1974 |
new OO.ui.PopupButtonWidget( { |
1975 |
icon: 'info', |
1976 |
framed: false, |
1977 |
popup: { |
1978 |
head: true, |
1979 |
label: 'More information', |
Error |
Row 1980, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
1980 |
$content: $( '<p>' ).text( loremIpsum ), |
1981 |
padded: true, |
1982 |
align: 'forwards' |
1983 |
} |
1984 |
} ), |
1985 |
{ |
1986 |
label: 'PopupButtonWidget (frameless, with popup head align: forwards)\u200E', |
1987 |
align: 'top' |
1988 |
} |
1989 |
), |
1990 |
new OO.ui.FieldLayout( |
1991 |
new OO.ui.PopupButtonWidget( { |
1992 |
icon: 'info', |
1993 |
framed: false, |
1994 |
popup: { |
1995 |
head: true, |
1996 |
label: 'More information', |
Error |
Row 1997, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
1997 |
$content: $( '<p>' ).text( loremIpsum ), |
1998 |
padded: true, |
1999 |
align: 'center' |
2000 |
} |
2001 |
} ), |
2002 |
{ |
2003 |
label: 'PopupButtonWidget (frameless, with popup head align: center)\u200E', |
2004 |
align: 'top' |
2005 |
} |
2006 |
), |
2007 |
new OO.ui.FieldLayout( |
2008 |
new OO.ui.PopupButtonWidget( { |
2009 |
icon: 'info', |
2010 |
framed: false, |
2011 |
popup: { |
2012 |
head: true, |
2013 |
label: 'More information', |
2014 |
$content: $( '<p>' + loremIpsum + '</p><ul><li>Item one</li><li>Item two</li><li>Item three</li><li>Item four</li></ul><p>Even more text here which might well be clipped off the visible area.</p>' ), |
2015 |
$footer: $( '<p>And maybe a footer whilst we\'re at it?</p>' ), |
2016 |
padded: true, |
2017 |
align: 'forwards' |
2018 |
} |
2019 |
} ), |
2020 |
{ |
2021 |
label: 'PopupButtonWidget (frameless, with popup head and footer, align: forwards)\u200E', |
2022 |
align: 'top' |
2023 |
} |
2024 |
), |
2025 |
new OO.ui.FieldLayout( |
2026 |
new OO.ui.PopupButtonWidget( { |
2027 |
icon: 'menu', |
2028 |
label: 'Options', |
2029 |
popup: { |
Error |
Row 2030, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
2030 |
$content: $( '<p>' ).text( loremIpsum ), |
2031 |
padded: true, |
2032 |
align: 'forwards' |
2033 |
} |
2034 |
} ), |
2035 |
{ |
2036 |
label: 'PopupButtonWidget (framed, no popup head, align: forwards)\u200E', |
2037 |
align: 'top' |
2038 |
} |
2039 |
), |
2040 |
new OO.ui.FieldLayout( |
2041 |
new OO.ui.PopupButtonWidget( { |
2042 |
icon: 'menu', |
2043 |
label: 'Options', |
2044 |
popup: { |
Error |
Row 2045, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
2045 |
$content: $( '<p>' ).text( loremIpsum ), |
2046 |
padded: true, |
2047 |
align: 'backwards' |
2048 |
} |
2049 |
} ), |
2050 |
{ |
2051 |
label: 'PopupButtonWidget (framed, no popup head, align: backwards)\u200E', |
2052 |
align: 'top' |
2053 |
} |
2054 |
), |
2055 |
new OO.ui.FieldLayout( |
2056 |
new OO.ui.PopupButtonWidget( { |
2057 |
icon: 'menu', |
2058 |
label: 'Options', |
2059 |
popup: { |
Error |
Row 2060, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
2060 |
$content: $( '<p>' ).text( loremIpsum ), |
2061 |
padded: true, |
2062 |
align: 'center' |
2063 |
} |
2064 |
} ), |
2065 |
{ |
2066 |
label: 'PopupButtonWidget (framed, no popup head, align: center)\u200E', |
2067 |
align: 'top' |
2068 |
} |
2069 |
), |
2070 |
new OO.ui.FieldLayout( |
2071 |
new OO.ui.PopupButtonWidget( { |
2072 |
icon: 'menu', |
2073 |
label: 'Options', |
2074 |
popup: { |
Error |
Row 2075, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
2075 |
$content: $( '<p>' ).text( loremIpsum ), |
2076 |
padded: true, |
2077 |
align: 'center', |
2078 |
position: 'above' |
2079 |
} |
2080 |
} ), |
2081 |
{ |
2082 |
label: 'PopupButtonWidget (framed, no popup head, position: above)\u200E', |
2083 |
align: 'top' |
2084 |
} |
2085 |
), |
2086 |
new OO.ui.FieldLayout( |
2087 |
new OO.ui.PopupButtonWidget( { |
2088 |
icon: 'menu', |
2089 |
label: 'Options', |
2090 |
popup: { |
Error |
Row 2091, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
2091 |
$content: $( '<p>' ).text( loremIpsum ), |
2092 |
padded: true, |
2093 |
align: 'center', |
2094 |
position: 'before' |
2095 |
} |
2096 |
} ), |
2097 |
{ |
2098 |
label: 'PopupButtonWidget (framed, no popup head, position: before)\u200E', |
2099 |
align: 'top' |
2100 |
} |
2101 |
), |
2102 |
new OO.ui.FieldLayout( |
2103 |
new OO.ui.PopupButtonWidget( { |
2104 |
icon: 'menu', |
2105 |
label: 'Options', |
2106 |
popup: { |
Error |
Row 2107, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
2107 |
$content: $( '<p>' ).text( loremIpsum ), |
2108 |
padded: true, |
2109 |
align: 'center', |
2110 |
position: 'after' |
2111 |
} |
2112 |
} ), |
2113 |
{ |
2114 |
label: 'PopupButtonWidget (framed, no popup head, position: after)\u200E', |
2115 |
align: 'top' |
2116 |
} |
2117 |
), |
2118 |
new OO.ui.FieldLayout( |
2119 |
new OO.ui.PopupButtonWidget( { |
2120 |
icon: 'menu', |
2121 |
label: 'Options', |
2122 |
popup: { |
Error |
Row 2123, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
2123 |
$content: $( '<p>' ).text( loremIpsum ), |
2124 |
padded: true, |
2125 |
anchor: false, |
2126 |
align: 'center' |
2127 |
} |
2128 |
} ), |
2129 |
{ |
2130 |
label: 'PopupButtonWidget (no anchor, align: center)\u200E', |
2131 |
align: 'top' |
2132 |
} |
2133 |
), |
2134 |
new OO.ui.FieldLayout( |
2135 |
new OO.ui.PopupButtonWidget( { |
2136 |
icon: 'menu', |
2137 |
label: 'Options', |
2138 |
popup: { |
Error |
Row 2139, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
2139 |
$content: $( '<p>' ).text( loremIpsum ), |
2140 |
padded: true, |
2141 |
anchor: false, |
2142 |
align: 'forwards' |
2143 |
} |
2144 |
} ), |
2145 |
{ |
2146 |
label: 'PopupButtonWidget (no anchor, align: forwards)\u200E', |
2147 |
align: 'top' |
2148 |
} |
2149 |
), |
2150 |
new OO.ui.FieldLayout( |
2151 |
new OO.ui.PopupButtonWidget( { |
2152 |
icon: 'menu', |
2153 |
label: 'Options', |
2154 |
popup: { |
Error |
Row 2155, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
2155 |
$content: $( '<p>' ).text( loremIpsum ), |
2156 |
padded: true, |
2157 |
anchor: false, |
2158 |
align: 'backwards' |
2159 |
} |
2160 |
} ), |
2161 |
{ |
2162 |
label: 'PopupButtonWidget (no anchor, align: backwards)\u200E', |
2163 |
align: 'top' |
2164 |
} |
2165 |
) |
2166 |
] |
2167 |
} ), |
2168 |
new OO.ui.FieldsetLayout( { |
2169 |
id: 'demo-section-draggable', |
2170 |
label: 'Draggable', |
2171 |
items: [ |
2172 |
new OO.ui.FieldLayout( |
2173 |
new Demo.DraggableGroupWidget( { |
2174 |
orientation: 'horizontal', |
2175 |
items: horizontalDragItems |
2176 |
} ), |
2177 |
{ |
2178 |
label: 'DraggableGroupWidget (horizontal)\u200E', |
2179 |
align: 'top' |
2180 |
} |
2181 |
), |
2182 |
new OO.ui.FieldLayout( |
2183 |
new Demo.DraggableGroupWidget( { |
2184 |
items: verticalDragItems |
2185 |
} ), |
2186 |
{ |
2187 |
label: 'DraggableGroupWidget (vertical)\u200E', |
2188 |
align: 'top' |
2189 |
} |
2190 |
), |
2191 |
new OO.ui.FieldLayout( |
2192 |
new Demo.DraggableGroupWidget( { |
2193 |
items: verticalHandledDragItems |
2194 |
} ), |
2195 |
{ |
2196 |
label: 'DraggableGroupWidget with handles (vertical)\u200E', |
2197 |
align: 'top' |
2198 |
} |
2199 |
) |
2200 |
] |
2201 |
} ), |
2202 |
new OO.ui.FieldsetLayout( { |
2203 |
id: 'demo-section-progressBar', |
2204 |
label: 'Progress bar', |
2205 |
items: [ |
2206 |
new OO.ui.FieldLayout( |
2207 |
new OO.ui.ProgressBarWidget( { |
2208 |
progress: 33 |
2209 |
} ), |
2210 |
{ |
2211 |
label: 'Progress bar', |
2212 |
align: 'top' |
2213 |
} |
2214 |
), |
2215 |
new OO.ui.FieldLayout( |
2216 |
new OO.ui.ProgressBarWidget( { |
2217 |
progress: 50, |
2218 |
disabled: true |
2219 |
} ), |
2220 |
{ |
2221 |
label: 'Progress bar (disabled)\u200E', |
2222 |
align: 'top' |
2223 |
} |
2224 |
), |
2225 |
new OO.ui.FieldLayout( |
2226 |
new OO.ui.ProgressBarWidget( { |
2227 |
progress: false |
2228 |
} ), |
2229 |
{ |
2230 |
label: 'Progress bar (indeterminate)\u200E', |
2231 |
align: 'top' |
2232 |
} |
2233 |
) |
2234 |
] |
2235 |
} ), |
2236 |
new OO.ui.FieldsetLayout( { |
2237 |
id: 'demo-section-others', |
2238 |
label: 'Other widgets', |
2239 |
items: [ |
2240 |
new OO.ui.FieldLayout( |
2241 |
new OO.ui.IconWidget( { |
2242 |
icon: 'search', |
2243 |
title: 'Search icon' |
2244 |
} ), |
2245 |
{ |
2246 |
label: 'IconWidget (normal)\u200E', |
2247 |
align: 'top' |
2248 |
} |
2249 |
), |
2250 |
new OO.ui.FieldLayout( |
2251 |
new OO.ui.IconWidget( { |
2252 |
icon: 'trash', |
2253 |
flags: 'destructive', |
2254 |
title: 'Remove icon' |
2255 |
} ), |
2256 |
{ |
2257 |
label: 'IconWidget (flagged)\u200E', |
2258 |
align: 'top' |
2259 |
} |
2260 |
), |
2261 |
new OO.ui.FieldLayout( |
2262 |
new OO.ui.IconWidget( { |
2263 |
icon: 'search', |
2264 |
title: 'Search icon', |
2265 |
disabled: true |
2266 |
} ), |
2267 |
{ |
2268 |
label: 'IconWidget (disabled)\u200E', |
2269 |
align: 'top' |
2270 |
} |
2271 |
), |
2272 |
new OO.ui.FieldLayout( |
2273 |
new OO.ui.IndicatorWidget( { |
2274 |
indicator: 'clear', |
2275 |
title: 'Required indicator' |
2276 |
} ), |
2277 |
{ |
2278 |
label: 'IndicatorWidget (normal)\u200E', |
2279 |
align: 'top' |
2280 |
} |
2281 |
), |
2282 |
new OO.ui.FieldLayout( |
2283 |
new OO.ui.IndicatorWidget( { |
2284 |
indicator: 'clear', |
2285 |
title: 'Required indicator', |
2286 |
disabled: true |
2287 |
} ), |
2288 |
{ |
2289 |
label: 'IndicatorWidget (disabled)\u200E', |
2290 |
align: 'top' |
2291 |
} |
2292 |
), |
2293 |
new OO.ui.FieldLayout( |
2294 |
new OO.ui.LabelWidget( { |
2295 |
label: 'Label' |
2296 |
} ), |
2297 |
{ |
2298 |
label: 'LabelWidget (normal)\u200E', |
2299 |
align: 'top' |
2300 |
} |
2301 |
), |
2302 |
new OO.ui.FieldLayout( |
2303 |
new OO.ui.LabelWidget( { |
2304 |
label: 'Label', |
2305 |
disabled: true |
2306 |
} ), |
2307 |
{ |
2308 |
label: 'LabelWidget (disabled)\u200E', |
2309 |
align: 'top' |
2310 |
} |
2311 |
), |
2312 |
new OO.ui.FieldLayout( |
2313 |
new OO.ui.LabelWidget( { |
2314 |
label: new OO.ui.HtmlSnippet( '<b>Fancy</b> <i>text</i> <u>formatting</u>!' ) |
2315 |
} ), |
2316 |
{ |
2317 |
label: 'LabelWidget (with HTML)\u200E', |
2318 |
align: 'top' |
2319 |
} |
2320 |
), |
2321 |
new OO.ui.FieldLayout( |
2322 |
labelForTextInput, |
2323 |
{ |
2324 |
label: 'LabelWidget (with an associated TextInputWidget)\u200E', |
2325 |
align: 'top' |
2326 |
} |
2327 |
), |
2328 |
new OO.ui.FieldLayout( |
2329 |
textInputForLabel, |
2330 |
{ |
2331 |
label: 'TextInputWidget (with an associated label)\u200E', |
2332 |
align: 'top' |
2333 |
} |
2334 |
), |
2335 |
new OO.ui.FieldLayout( |
2336 |
labelForRadioSelectInput, |
2337 |
{ |
2338 |
label: 'LabelWidget (with an associated RadioSelectInputWidget)\u200E', |
2339 |
align: 'top' |
2340 |
} |
2341 |
), |
2342 |
new OO.ui.FieldLayout( |
2343 |
radioSelectInputForLabel, |
2344 |
{ |
2345 |
label: 'RadioSelectInputWidget (with an associated label)\u200E', |
2346 |
align: 'top' |
2347 |
} |
2348 |
) |
2349 |
] |
2350 |
} ), |
2351 |
new OO.ui.FieldsetLayout( { |
2352 |
id: 'demo-section-fieldLayouts', |
2353 |
label: 'Field layouts', |
2354 |
icon: 'tag', |
2355 |
help: loremIpsum, |
2356 |
items: [ |
2357 |
new OO.ui.FieldLayout( |
2358 |
new OO.ui.ButtonWidget( { |
2359 |
label: 'Button' |
2360 |
} ), |
2361 |
{ |
2362 |
label: 'FieldLayout with help', |
2363 |
help: loremIpsum, |
2364 |
align: 'top' |
2365 |
} |
2366 |
), |
2367 |
new OO.ui.FieldLayout( |
2368 |
new OO.ui.ButtonWidget( { |
2369 |
label: 'Button' |
2370 |
} ), |
2371 |
{ |
2372 |
label: 'FieldLayout with inlined help', |
2373 |
help: 'This is some inlined help. Assistive (optional) text, that isn\'t needed to understand the widget\'s purpose.', |
2374 |
helpInline: true, |
2375 |
align: 'top' |
2376 |
} |
2377 |
), |
2378 |
new OO.ui.FieldLayout( |
2379 |
new OO.ui.ButtonWidget( { |
2380 |
label: 'Button' |
2381 |
} ), |
2382 |
{ |
2383 |
label: 'FieldLayout with rich text help', |
2384 |
help: new OO.ui.HtmlSnippet( '<b>Bold text</b> is helpful!' ), |
2385 |
align: 'top' |
2386 |
} |
2387 |
), |
2388 |
new OO.ui.FieldLayout( |
2389 |
new OO.ui.ButtonWidget( { |
2390 |
label: 'Button' |
2391 |
} ), |
2392 |
{ |
2393 |
label: 'FieldLayout with inlined rich text help', |
2394 |
help: new OO.ui.HtmlSnippet( '<b>Strong text</b> is helpful! It should only contain assistive (optional) text.' ), |
2395 |
helpInline: true, |
2396 |
align: 'top' |
2397 |
} |
2398 |
), |
2399 |
new OO.ui.FieldLayout( |
2400 |
new OO.ui.ButtonWidget( { |
2401 |
label: 'Button' |
2402 |
} ), |
2403 |
{ |
2404 |
label: 'FieldLayout with title', |
2405 |
title: 'Field title text', |
2406 |
align: 'top' |
2407 |
} |
2408 |
), |
2409 |
new OO.ui.FieldLayout( |
2410 |
new OO.ui.ButtonWidget( { |
2411 |
label: 'Button' |
2412 |
} ), |
2413 |
{ |
Error |
Row 2414, Column 14: "Prefer textContent to $.text"
jquery/no-text
|
2414 |
label: $( '<i>' ).text( 'FieldLayout with rich text label' ), |
2415 |
align: 'top' |
2416 |
} |
2417 |
), |
2418 |
new OO.ui.ActionFieldLayout( |
2419 |
new OO.ui.TextInputWidget(), |
2420 |
new OO.ui.ButtonWidget( { |
2421 |
label: 'Button' |
2422 |
} ), |
2423 |
{ |
2424 |
label: 'ActionFieldLayout aligned top', |
2425 |
align: 'top' |
2426 |
} |
2427 |
), |
2428 |
new OO.ui.FieldLayout( |
2429 |
new OO.ui.TextInputWidget(), |
2430 |
{ |
2431 |
label: 'FieldLayout aligned top with help', |
2432 |
help: loremIpsum, |
2433 |
align: 'top' |
2434 |
} |
2435 |
), |
2436 |
new OO.ui.FieldLayout( |
2437 |
new OO.ui.TextInputWidget(), |
2438 |
{ |
2439 |
label: 'FieldLayout aligned top with inlined help', |
2440 |
help: 'This is some inlined help. Assistive (optional) text, that isn\'t needed to understand the widget\'s purpose.', |
2441 |
helpInline: true, |
2442 |
align: 'top' |
2443 |
} |
2444 |
), |
2445 |
new OO.ui.ActionFieldLayout( |
2446 |
new OO.ui.TextInputWidget(), |
2447 |
new OO.ui.ButtonWidget( { |
2448 |
label: 'Button' |
2449 |
} ), |
2450 |
{ |
2451 |
label: 'ActionFieldLayout aligned top with help', |
2452 |
help: loremIpsum, |
2453 |
align: 'top' |
2454 |
} |
2455 |
), |
2456 |
new OO.ui.ActionFieldLayout( |
2457 |
new OO.ui.CheckboxInputWidget( { selected: true } ), |
2458 |
new OO.ui.ButtonWidget( { |
2459 |
label: 'Button' |
2460 |
} ), |
2461 |
{ |
2462 |
label: 'ActionFieldLayout aligned inline', |
2463 |
align: 'inline' |
2464 |
} |
2465 |
), |
2466 |
new OO.ui.FieldLayout( |
2467 |
new OO.ui.CheckboxInputWidget( { selected: true } ), |
2468 |
{ |
2469 |
label: 'FieldLayout aligned inline with help', |
2470 |
help: loremIpsum, |
2471 |
align: 'inline' |
2472 |
} |
2473 |
), |
2474 |
new OO.ui.ActionFieldLayout( |
2475 |
new OO.ui.CheckboxInputWidget( { selected: true } ), |
2476 |
new OO.ui.ButtonWidget( { |
2477 |
label: 'Button' |
2478 |
} ), |
2479 |
{ |
2480 |
label: 'ActionFieldLayout aligned inline with help', |
2481 |
help: loremIpsum, |
2482 |
align: 'inline' |
2483 |
} |
2484 |
), |
2485 |
new OO.ui.ActionFieldLayout( |
2486 |
new OO.ui.TextInputWidget(), |
2487 |
new OO.ui.ButtonWidget( { |
2488 |
label: 'Button' |
2489 |
} ), |
2490 |
{ |
2491 |
label: 'ActionFieldLayout aligned left', |
2492 |
align: 'left' |
2493 |
} |
2494 |
), |
2495 |
new OO.ui.FieldLayout( |
2496 |
new OO.ui.TextInputWidget(), |
2497 |
{ |
2498 |
label: 'FieldLayout aligned left with help', |
2499 |
help: loremIpsum, |
2500 |
align: 'left' |
2501 |
} |
2502 |
), |
2503 |
new OO.ui.FieldLayout( |
2504 |
new OO.ui.TextInputWidget(), |
2505 |
{ |
2506 |
label: 'FieldLayout aligned left with inline help', |
2507 |
help: 'This is some inlined help', |
2508 |
helpInline: true, |
2509 |
align: 'left' |
2510 |
} |
2511 |
), |
2512 |
new OO.ui.ActionFieldLayout( |
2513 |
new OO.ui.TextInputWidget(), |
2514 |
new OO.ui.ButtonWidget( { |
2515 |
label: 'Button' |
2516 |
} ), |
2517 |
{ |
2518 |
label: 'ActionFieldLayout aligned left with help', |
2519 |
help: loremIpsum, |
2520 |
align: 'left' |
2521 |
} |
2522 |
), |
2523 |
new OO.ui.ActionFieldLayout( |
2524 |
new OO.ui.TextInputWidget(), |
2525 |
new OO.ui.ButtonWidget( { |
2526 |
label: 'Button' |
2527 |
} ), |
2528 |
{ |
2529 |
label: 'ActionFieldLayout aligned right', |
2530 |
align: 'right' |
2531 |
} |
2532 |
), |
2533 |
new OO.ui.FieldLayout( |
2534 |
new OO.ui.TextInputWidget(), |
2535 |
{ |
2536 |
label: 'FieldLayout aligned right with help', |
2537 |
help: loremIpsum, |
2538 |
align: 'right' |
2539 |
} |
2540 |
), |
2541 |
new OO.ui.FieldLayout( |
2542 |
new OO.ui.TextInputWidget(), |
2543 |
{ |
2544 |
label: 'FieldLayout aligned right with inlined help', |
2545 |
help: 'This is some inlined help', |
2546 |
helpInline: true, |
2547 |
align: 'right' |
2548 |
} |
2549 |
), |
2550 |
new OO.ui.ActionFieldLayout( |
2551 |
new OO.ui.TextInputWidget(), |
2552 |
new OO.ui.ButtonWidget( { |
2553 |
label: 'Button' |
2554 |
} ), |
2555 |
{ |
2556 |
label: 'ActionFieldLayout aligned right with help', |
2557 |
help: loremIpsum, |
2558 |
align: 'right' |
2559 |
} |
2560 |
), |
2561 |
new OO.ui.FieldLayout( |
2562 |
new OO.ui.TextInputWidget(), |
2563 |
{ |
2564 |
label: 'FieldLayout aligned top with a very long label. ' + loremIpsum, |
2565 |
help: loremIpsum, |
2566 |
align: 'top' |
2567 |
} |
2568 |
), |
2569 |
new OO.ui.FieldLayout( |
2570 |
new OO.ui.CheckboxInputWidget( { selected: true } ), |
2571 |
{ |
2572 |
label: 'FieldLayout aligned inline with a very long label. ' + loremIpsum, |
2573 |
help: loremIpsum, |
2574 |
align: 'inline' |
2575 |
} |
2576 |
), |
2577 |
new OO.ui.FieldLayout( |
2578 |
new OO.ui.TextInputWidget(), |
2579 |
{ |
2580 |
label: 'FieldLayout aligned left with a very long label. ' + loremIpsum, |
2581 |
help: loremIpsum, |
2582 |
align: 'left' |
2583 |
} |
2584 |
), |
2585 |
new OO.ui.FieldLayout( |
2586 |
new OO.ui.TextInputWidget(), |
2587 |
{ |
2588 |
label: 'FieldLayout aligned right with a very long label. ' + loremIpsum, |
2589 |
help: loremIpsum, |
2590 |
align: 'right' |
2591 |
} |
2592 |
), |
2593 |
new OO.ui.FieldLayout( |
2594 |
new OO.ui.TextInputWidget(), |
2595 |
{ |
2596 |
label: 'FieldLayout aligned right with a very long label and inline help. ' + loremIpsum, |
2597 |
help: 'This is some inlined help', |
2598 |
helpInline: true, |
2599 |
align: 'right' |
2600 |
} |
2601 |
), |
2602 |
new OO.ui.FieldLayout( |
2603 |
new OO.ui.TextInputWidget( { |
2604 |
value: '' |
2605 |
} ), |
2606 |
{ |
2607 |
label: 'FieldLayout with notice', |
2608 |
notices: [ 'Please input a number.' ], |
2609 |
align: 'top' |
2610 |
} |
2611 |
), |
2612 |
new OO.ui.FieldLayout( |
2613 |
new OO.ui.TextInputWidget( { |
2614 |
value: 'Foo' |
2615 |
} ), |
2616 |
{ |
2617 |
label: 'FieldLayout with error message', |
2618 |
errors: [ 'The value must be a number. It is more than necessary. You can\'t go on without putting a number into this input field.' ], |
2619 |
align: 'top' |
2620 |
} |
2621 |
), |
2622 |
new OO.ui.FieldLayout( |
2623 |
new OO.ui.TextInputWidget( { |
2624 |
value: 'Foo' |
2625 |
} ), |
2626 |
{ |
2627 |
label: 'FieldLayout with notice and error message', |
2628 |
notices: [ 'Please input a number.' ], |
2629 |
errors: [ 'The value must be a number.' ], |
2630 |
align: 'top' |
2631 |
} |
2632 |
), |
2633 |
fieldLayoutForValidation |
2634 |
] |
2635 |
} ), |
2636 |
new OO.ui.FieldsetLayout( { |
2637 |
id: 'demo-section-horizontalLayout', |
2638 |
label: 'HorizontalLayout', |
2639 |
items: [ |
2640 |
new OO.ui.FieldLayout( |
2641 |
new OO.ui.Widget( { |
2642 |
content: [ new OO.ui.HorizontalLayout( { |
2643 |
items: [ |
2644 |
new OO.ui.ButtonWidget( { label: 'Button' } ), |
2645 |
new OO.ui.ButtonGroupWidget( { items: [ |
2646 |
new OO.ui.ToggleButtonWidget( { label: 'A' } ), |
2647 |
new OO.ui.ToggleButtonWidget( { label: 'B' } ) |
2648 |
] } ), |
2649 |
new OO.ui.ButtonInputWidget( { label: 'ButtonInput' } ), |
2650 |
new OO.ui.TextInputWidget( { value: 'TextInput' } ), |
2651 |
new OO.ui.DropdownInputWidget( { options: [ |
2652 |
{ |
2653 |
label: 'DropdownInput', |
2654 |
data: null |
2655 |
} |
2656 |
] } ), |
2657 |
new OO.ui.CheckboxInputWidget( { selected: true } ), |
2658 |
new OO.ui.RadioInputWidget( { selected: true } ), |
2659 |
new OO.ui.LabelWidget( { label: 'Label' } ) |
2660 |
] |
2661 |
} ) ] |
2662 |
} ), |
2663 |
{ |
2664 |
label: 'Multiple widgets shown as a single line, ' + |
2665 |
'as used in compact forms or in parts of a bigger widget.', |
2666 |
align: 'top' |
2667 |
} |
2668 |
) |
2669 |
] |
2670 |
} ), |
2671 |
new OO.ui.FieldsetLayout( { |
2672 |
id: 'demo-section-other-layouts', |
2673 |
label: 'Other layouts', |
2674 |
items: [ |
2675 |
new OO.ui.FieldLayout( |
2676 |
new OO.ui.Widget( { |
2677 |
content: [ |
2678 |
new OO.ui.PanelLayout( { |
2679 |
expanded: false, |
2680 |
framed: true, |
2681 |
content: [ |
2682 |
new OO.ui.BookletLayout( { |
2683 |
expanded: false, |
2684 |
outlined: true |
2685 |
} ).addPages( [ |
2686 |
new Demo.SamplePage( 'first', { |
2687 |
expanded: false, |
2688 |
label: 'One' |
2689 |
} ), |
2690 |
new Demo.SamplePage( 'second', { |
2691 |
expanded: false, |
2692 |
label: 'Two' |
2693 |
} ), |
2694 |
new Demo.SamplePage( 'third', { |
2695 |
expanded: false, |
2696 |
label: 'Three' |
2697 |
} ), |
2698 |
new Demo.SamplePage( 'fourth', { |
2699 |
expanded: false, |
2700 |
label: 'Four' |
2701 |
} ), |
2702 |
new Demo.SamplePage( 'long', { |
2703 |
expanded: false, |
2704 |
label: 'Long', |
2705 |
content: [ |
Error |
Row 2706, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
2706 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 2707, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
2707 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 2708, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
2708 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 2709, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
2709 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 2710, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
2710 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 2711, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
2711 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 2712, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
2712 |
$( '<p>' ).text( loremIpsum ) |
2713 |
] |
2714 |
} ) |
2715 |
] ) |
2716 |
] |
2717 |
} ) |
2718 |
] |
2719 |
} ), |
2720 |
{ |
2721 |
label: 'Outlined BookletLayout', |
2722 |
align: 'top' |
2723 |
} |
2724 |
), |
2725 |
new OO.ui.FieldLayout( |
2726 |
new OO.ui.Widget( { |
2727 |
content: [ |
2728 |
new OO.ui.PanelLayout( { |
2729 |
expanded: false, |
2730 |
framed: true, |
2731 |
content: [ |
2732 |
new OO.ui.IndexLayout( { |
2733 |
expanded: false |
2734 |
} ).addTabPanels( [ |
2735 |
new Demo.SampleTabPanel( 'first', { |
2736 |
expanded: false, |
2737 |
label: 'One tab' |
2738 |
} ), |
2739 |
new Demo.SampleTabPanel( 'second', { |
2740 |
expanded: false, |
2741 |
label: 'Two tab' |
2742 |
} ), |
2743 |
new Demo.SampleTabPanel( 'third', { |
2744 |
expanded: false, |
2745 |
label: 'Three tab' |
2746 |
} ), |
2747 |
new Demo.SampleTabPanel( 'fourth', { |
2748 |
expanded: false, |
2749 |
label: 'Four tab' |
2750 |
} ), |
2751 |
new Demo.SampleTabPanel( 'long', { |
2752 |
expanded: false, |
2753 |
label: 'Long tab', |
2754 |
content: [ |
Error |
Row 2755, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
2755 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 2756, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
2756 |
$( '<p>' ).text( loremIpsum ), |
Error |
Row 2757, Column 13: "Prefer textContent to $.text"
jquery/no-text
|
2757 |
$( '<p>' ).text( loremIpsum ) |
2758 |
] |
2759 |
} ) |
2760 |
] ) |
2761 |
] |
2762 |
} ) |
2763 |
] |
2764 |
} ), |
2765 |
{ |
2766 |
label: 'IndexLayout', |
2767 |
align: 'top' |
2768 |
} |
2769 |
) |
2770 |
] |
2771 |
} ), |
2772 |
new OO.ui.FormLayout( { |
2773 |
method: 'GET', |
2774 |
action: 'demos.php', |
2775 |
items: [ |
2776 |
new OO.ui.FieldsetLayout( { |
2777 |
id: 'demo-section-formLayout', |
2778 |
label: 'Form layout (compounded example)', |
2779 |
items: [ |
2780 |
new OO.ui.FieldLayout( |
2781 |
new OO.ui.TextInputWidget( { |
2782 |
name: 'username' |
2783 |
} ), |
2784 |
{ |
2785 |
label: 'User name', |
2786 |
align: 'top' |
2787 |
} |
2788 |
), |
2789 |
new OO.ui.FieldLayout( |
2790 |
new OO.ui.TextInputWidget( { |
2791 |
name: 'password', |
2792 |
type: 'password' |
2793 |
} ), |
2794 |
{ |
2795 |
label: 'Password', |
2796 |
align: 'top' |
2797 |
} |
2798 |
), |
2799 |
new OO.ui.FieldLayout( |
2800 |
new OO.ui.ButtonSelectWidget( { |
2801 |
items: [ |
2802 |
new OO.ui.ButtonOptionWidget( { |
2803 |
label: 'One' |
2804 |
} ), |
2805 |
new OO.ui.ButtonOptionWidget( { |
2806 |
label: 'Two' |
2807 |
} ), |
2808 |
new OO.ui.ButtonOptionWidget( { |
2809 |
indicator: 'clear', |
2810 |
label: 'Three' |
2811 |
} ) |
2812 |
] |
2813 |
} ), |
2814 |
{ |
2815 |
label: 'Select one of multiple ButtonSelectWidget Buttons', |
2816 |
align: 'top' |
2817 |
} |
2818 |
), |
2819 |
new OO.ui.FieldLayout( |
2820 |
new OO.ui.SelectFileWidget( {} ), |
2821 |
{ |
2822 |
label: 'Select a file with SelectFileWidget\u200E', |
2823 |
align: 'top' |
2824 |
} |
2825 |
), |
2826 |
new OO.ui.FieldLayout( |
2827 |
new OO.ui.MenuTagMultiselectWidget( { |
2828 |
menu: { |
2829 |
items: [ |
2830 |
new OO.ui.MenuOptionWidget( { data: 'abc', label: 'Abc Label' } ), |
2831 |
new OO.ui.MenuOptionWidget( { data: 'def', label: 'Def Label' } ), |
2832 |
new OO.ui.MenuOptionWidget( { data: 'ghi', label: 'Ghi Label' } ) |
2833 |
] |
2834 |
}, |
2835 |
selected: [ |
2836 |
{ data: 'abc', label: 'Abc Label' }, |
2837 |
{ data: 'def', label: 'Def Label' } |
2838 |
] |
2839 |
} ), |
2840 |
{ |
2841 |
label: 'Select from multiple TagMultiselectWidget items\u200E', |
2842 |
align: 'top' |
2843 |
} |
2844 |
), |
2845 |
new OO.ui.FieldLayout( |
2846 |
new OO.ui.RadioSelectWidget( { |
2847 |
items: [ |
2848 |
new OO.ui.RadioOptionWidget( { |
2849 |
data: 'mouse', |
2850 |
label: 'Mouse' |
2851 |
} ), |
2852 |
new OO.ui.RadioOptionWidget( { |
2853 |
data: 'elephant', |
2854 |
label: 'Elephant' |
2855 |
} ) |
2856 |
] |
2857 |
} ), |
2858 |
{ |
2859 |
align: 'top', |
2860 |
label: 'Toggle the RadioSelectWidget' |
2861 |
} |
2862 |
), |
2863 |
new OO.ui.FieldLayout( |
2864 |
new OO.ui.ToggleSwitchWidget( { value: true } ), |
2865 |
{ |
2866 |
label: 'Switch the ToggleSwitchWidget (checked)', |
2867 |
align: 'right' |
2868 |
} |
2869 |
), |
2870 |
new OO.ui.FieldLayout( |
2871 |
new OO.ui.CheckboxInputWidget( { |
2872 |
name: 'rememberme', |
2873 |
selected: true |
2874 |
} ), |
2875 |
{ |
2876 |
label: 'Remember me', |
2877 |
align: 'inline' |
2878 |
} |
2879 |
), |
2880 |
new OO.ui.FieldLayout( |
2881 |
new OO.ui.HiddenInputWidget( { |
2882 |
name: 'hidden', |
2883 |
value: 'hidden value' |
2884 |
} ) |
2885 |
), |
2886 |
new OO.ui.FieldLayout( |
2887 |
new OO.ui.ButtonInputWidget( { |
2888 |
type: 'submit', |
2889 |
label: 'Submit form' |
2890 |
} ) |
2891 |
) |
2892 |
] |
2893 |
} ), |
2894 |
new OO.ui.FieldsetLayout( { |
2895 |
label: null, |
2896 |
items: [ |
2897 |
new OO.ui.FieldLayout( |
2898 |
new OO.ui.TextInputWidget(), |
2899 |
{ |
2900 |
label: 'Summary', |
2901 |
align: 'top' |
2902 |
} |
2903 |
), |
2904 |
new OO.ui.FieldLayout( |
2905 |
new OO.ui.Widget( { |
2906 |
content: [ new OO.ui.HorizontalLayout( { |
2907 |
items: [ |
2908 |
new OO.ui.ButtonInputWidget( { |
2909 |
name: 'login', |
2910 |
label: 'Log in', |
2911 |
type: 'submit', |
2912 |
flags: [ 'primary', 'progressive' ], |
2913 |
icon: 'userAvatar' |
2914 |
} ), |
2915 |
new OO.ui.ButtonWidget( { |
2916 |
framed: false, |
2917 |
flags: [ 'destructive' ], |
2918 |
label: 'Cancel' |
2919 |
} ), |
2920 |
new OO.ui.ButtonWidget( { |
2921 |
framed: false, |
2922 |
icon: 'tag', |
2923 |
label: 'Random icon button' |
2924 |
} ), |
2925 |
new OO.ui.ButtonWidget( { |
2926 |
framed: false, |
2927 |
icon: 'help', |
2928 |
title: 'Icon only' |
2929 |
} ) |
2930 |
] |
2931 |
} ) ] |
2932 |
} ), |
2933 |
{ |
2934 |
label: null, |
2935 |
align: 'top' |
2936 |
} |
2937 |
) |
2938 |
] |
2939 |
} ) |
2940 |
] |
2941 |
} ) |
2942 |
]; |
2943 |
|
Error |
Row 2944, Column 2: "Prefer Array#forEach to $.each"
jquery/no-each
|
2944 |
$.each( fieldsets, function ( i, fieldsetLayout ) { |
2945 |
var showLayoutCode = false; |
2946 |
|
2947 |
if ( |
2948 |
fieldsetLayout instanceof OO.ui.FormLayout || |
2949 |
fieldsetLayout.getLabel() === 'Field layouts' |
2950 |
) { |
2951 |
showLayoutCode = true; |
2952 |
} |
2953 |
|
Error |
Row 2954, Column 3: "Prefer Array#forEach to $.each"
jquery/no-each
|
2954 |
$.each( fieldsetLayout.getItems(), function ( j, fieldLayout ) { |
2955 |
fieldLayout.$element.append( |
2956 |
demo.buildLinkExample( fieldLayout, fieldsetLayout instanceof OO.ui.FormLayout ? fieldLayout : fieldsetLayout ), |
2957 |
demo.buildConsole( fieldLayout, 'layout', 'widget', showLayoutCode ) |
2958 |
); |
2959 |
} ); |
2960 |
} ); |
2961 |
|
2962 |
$demo.append( |
2963 |
new OO.ui.PanelLayout( { |
2964 |
expanded: false, |
2965 |
framed: true |
2966 |
} ).$element |
2967 |
.addClass( 'demo-container' ) |
2968 |
.attr( 'role', 'main' ) |
2969 |
.append( |
2970 |
$( fieldsets.map( function ( fieldset ) { return fieldset.$element[ 0 ]; } ) ) |
2971 |
) |
2972 |
); |
2973 |
|
2974 |
$overlay.appendTo( 'body' ); |
2975 |
|
2976 |
demo.once( 'destroy', function () { |
2977 |
// We are removing all of the widgets from the page, so also remove their "detached" |
2978 |
// menus and stuff, otherwise they can remain visible forever. |
2979 |
$overlay.remove(); |
2980 |
OO.ui.$defaultOverlay.empty(); |
2981 |
} ); |
2982 |
}; |
2983 |
|
|
|
/demos/tutorials/assets/init.js
|
4 problems (4 errors, 0 warnings)
|
Line |
Source |
1 |
// eslint-disable-next-line no-unused-vars |
2 |
var Widgets = {}; |
3 |
/* |
4 |
* Back to top button |
5 |
* Taken from https://codepen.io/desirecode/pen/MJPJqV/ |
6 |
*/ |
Error |
Row 7, Column 1: "$.ready is not allowed"
jquery/no-ready
|
7 |
$( document ).ready( function () { |
8 |
$( window ).scroll( function () { |
9 |
if ( $( this ).scrollTop() > 100 ) { |
Error |
Row 10, Column 4: "$.fadeIn is not allowed"
jquery/no-fade
|
10 |
$( '.scroll' ).fadeIn(); |
11 |
} else { |
Error |
Row 12, Column 4: "$.fadeOut is not allowed"
jquery/no-fade
|
12 |
$( '.scroll' ).fadeOut(); |
13 |
} |
14 |
} ); |
15 |
$( '.scroll' ).click( function () { |
Error |
Row 16, Column 3: "$.animate is not allowed"
jquery/no-animate
|
16 |
$( 'html, body' ).animate( { scrollTop: 0 }, 600 ); |
17 |
return false; |
18 |
} ); |
19 |
} ); |
20 |
|
|
|
/demos/tutorials/collection/basics1/snippets/app1.js
|
0 problems
|
|
/demos/tutorials/collection/basics1/snippets/app2.js
|
0 problems
|
|
/demos/tutorials/collection/basics1/snippets/app3.js
|
0 problems
|
|
/demos/tutorials/collection/basics1/snippets/app4.js
|
0 problems
|
|
/demos/tutorials/collection/basics2/snippets/app1.js
|
0 problems
|
|
/demos/tutorials/collection/basics2/snippets/app2-init.js
|
0 problems
|
|
/demos/tutorials/collection/basics2/snippets/app2-todo-item.js
|
0 problems
|
|
/demos/tutorials/collection/basics2/snippets/app3-init.js
|
0 problems
|
|
/demos/tutorials/collection/basics2/snippets/app3-todo-item.js
|
0 problems
|
|
/demos/tutorials/collection/basics2/snippets/app4-init.js
|
0 problems
|
|
/demos/tutorials/collection/basics2/snippets/app5-init.js
|
0 problems
|
|
/demos/tutorials/collection/basics2/snippets/app5-todo-item.js
|
0 problems
|
|
/demos/tutorials/collection/basics2/snippets/app5-todo-list.js
|
0 problems
|
|
/demos/tutorials/widgets/toolbar.js
|
0 problems
|
|
/src/ActionSet.js
|
0 problems
|
|
/src/core.js
|
8 problems (8 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 81, Column 4: "Prefer Array#filter to $.filter"
jquery/no-filter
|
Error |
Row 81, Column 4: "Prefer closest to $.parents"
jquery/no-parents
|
Error |
Row 82, Column 11: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 95, Column 7: "Prefer direct property access to $.prop"
jquery/no-prop
|
Error |
Row 108, Column 54: "Prefer getAttribute to $.attr"
jquery/no-attr
|
Error |
Row 126, Column 26: "Prefer querySelectorAll to $.find"
jquery/no-find
|
Error |
Row 133, Column 2: "Prefer Array#forEach to $.each"
jquery/no-each
|
Error |
Row 554, Column 27: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* Namespace for all classes, static methods and static properties. |
3 |
* |
4 |
* @class |
5 |
* @singleton |
6 |
*/ |
7 |
OO.ui = {}; |
8 |
|
9 |
OO.ui.bind = $.proxy; |
10 |
|
11 |
/** |
12 |
* @property {Object} |
13 |
*/ |
14 |
OO.ui.Keys = { |
15 |
UNDEFINED: 0, |
16 |
BACKSPACE: 8, |
17 |
DELETE: 46, |
18 |
LEFT: 37, |
19 |
RIGHT: 39, |
20 |
UP: 38, |
21 |
DOWN: 40, |
22 |
ENTER: 13, |
23 |
END: 35, |
24 |
HOME: 36, |
25 |
TAB: 9, |
26 |
PAGEUP: 33, |
27 |
PAGEDOWN: 34, |
28 |
ESCAPE: 27, |
29 |
SHIFT: 16, |
30 |
SPACE: 32 |
31 |
}; |
32 |
|
33 |
/** |
34 |
* Constants for MouseEvent.which |
35 |
* |
36 |
* @property {Object} |
37 |
*/ |
38 |
OO.ui.MouseButtons = { |
39 |
LEFT: 1, |
40 |
MIDDLE: 2, |
41 |
RIGHT: 3 |
42 |
}; |
43 |
|
44 |
/** |
45 |
* @property {number} |
46 |
* @private |
47 |
*/ |
48 |
OO.ui.elementId = 0; |
49 |
|
50 |
/** |
51 |
* Generate a unique ID for element |
52 |
* |
53 |
* @return {string} ID |
54 |
*/ |
55 |
OO.ui.generateElementId = function () { |
56 |
OO.ui.elementId++; |
57 |
return 'ooui-' + OO.ui.elementId; |
58 |
}; |
59 |
|
60 |
/** |
61 |
* Check if an element is focusable. |
62 |
* Inspired by :focusable in jQueryUI v1.11.4 - 2015-04-14 |
63 |
* |
64 |
* @param {jQuery} $element Element to test |
65 |
* @return {boolean} Element is focusable |
66 |
*/ |
67 |
OO.ui.isFocusableElement = function ( $element ) { |
68 |
var nodeName, |
69 |
element = $element[ 0 ]; |
70 |
|
71 |
// Anything disabled is not focusable |
72 |
if ( element.disabled ) { |
73 |
return false; |
74 |
} |
75 |
|
76 |
// Check if the element is visible |
77 |
if ( !( |
78 |
// This is quicker than calling $element.is( ':visible' ) |
79 |
$.expr.pseudos.visible( element ) && |
80 |
// Check that all parents are visible |
Error |
Row 81, Column 4: "Prefer Array#filter to $.filter"
jquery/no-filter
|
Error |
Row 81, Column 4: "Prefer closest to $.parents"
jquery/no-parents
|
81 |
!$element.parents().addBack().filter( function () { |
Error |
Row 82, Column 11: "Prefer getComputedStyle to $.css"
jquery/no-css
|
82 |
return $.css( this, 'visibility' ) === 'hidden'; |
83 |
} ).length |
84 |
) ) { |
85 |
return false; |
86 |
} |
87 |
|
88 |
// Check if the element is ContentEditable, which is the string 'true' |
89 |
if ( element.contentEditable === 'true' ) { |
90 |
return true; |
91 |
} |
92 |
|
93 |
// Anything with a non-negative numeric tabIndex is focusable. |
94 |
// Use .prop to avoid browser bugs |
Error |
Row 95, Column 7: "Prefer direct property access to $.prop"
jquery/no-prop
|
95 |
if ( $element.prop( 'tabIndex' ) >= 0 ) { |
96 |
return true; |
97 |
} |
98 |
|
99 |
// Some element types are naturally focusable |
100 |
// (indexOf is much faster than regex in Chrome and about the |
101 |
// same in FF: https://jsperf.com/regex-vs-indexof-array2) |
102 |
nodeName = element.nodeName.toLowerCase(); |
103 |
if ( [ 'input', 'select', 'textarea', 'button', 'object' ].indexOf( nodeName ) !== -1 ) { |
104 |
return true; |
105 |
} |
106 |
|
107 |
// Links and areas are focusable if they have an href |
Error |
Row 108, Column 54: "Prefer getAttribute to $.attr"
jquery/no-attr
|
108 |
if ( ( nodeName === 'a' || nodeName === 'area' ) && $element.attr( 'href' ) !== undefined ) { |
109 |
return true; |
110 |
} |
111 |
|
112 |
return false; |
113 |
}; |
114 |
|
115 |
/** |
116 |
* Find a focusable child |
117 |
* |
118 |
* @param {jQuery} $container Container to search in |
119 |
* @param {boolean} [backwards] Search backwards |
120 |
* @return {jQuery} Focusable child, or an empty jQuery object if none found |
121 |
*/ |
122 |
OO.ui.findFocusable = function ( $container, backwards ) { |
123 |
var $focusable = $( [] ), |
124 |
// $focusableCandidates is a superset of things that |
125 |
// could get matched by isFocusableElement |
Error |
Row 126, Column 26: "Prefer querySelectorAll to $.find"
jquery/no-find
|
126 |
$focusableCandidates = $container |
127 |
.find( 'input, select, textarea, button, object, a, area, [contenteditable], [tabindex]' ); |
128 |
|
129 |
if ( backwards ) { |
130 |
$focusableCandidates = Array.prototype.reverse.call( $focusableCandidates ); |
131 |
} |
132 |
|
Error |
Row 133, Column 2: "Prefer Array#forEach to $.each"
jquery/no-each
|
133 |
$focusableCandidates.each( function () { |
134 |
var $this = $( this ); |
135 |
if ( OO.ui.isFocusableElement( $this ) ) { |
136 |
$focusable = $this; |
137 |
return false; |
138 |
} |
139 |
} ); |
140 |
return $focusable; |
141 |
}; |
142 |
|
143 |
/** |
144 |
* Get the user's language and any fallback languages. |
145 |
* |
146 |
* These language codes are used to localize user interface elements in the user's language. |
147 |
* |
148 |
* In environments that provide a localization system, this function should be overridden to |
149 |
* return the user's language(s). The default implementation returns English (en) only. |
150 |
* |
151 |
* @return {string[]} Language codes, in descending order of priority |
152 |
*/ |
153 |
OO.ui.getUserLanguages = function () { |
154 |
return [ 'en' ]; |
155 |
}; |
156 |
|
157 |
/** |
158 |
* Get a value in an object keyed by language code. |
159 |
* |
160 |
* @param {Object.<string,Mixed>} obj Object keyed by language code |
161 |
* @param {string|null} [lang] Language code, if omitted or null defaults to any user language |
162 |
* @param {string} [fallback] Fallback code, used if no matching language can be found |
163 |
* @return {Mixed} Local value |
164 |
*/ |
165 |
OO.ui.getLocalValue = function ( obj, lang, fallback ) { |
166 |
var i, len, langs; |
167 |
|
168 |
// Requested language |
169 |
if ( obj[ lang ] ) { |
170 |
return obj[ lang ]; |
171 |
} |
172 |
// Known user language |
173 |
langs = OO.ui.getUserLanguages(); |
174 |
for ( i = 0, len = langs.length; i < len; i++ ) { |
175 |
lang = langs[ i ]; |
176 |
if ( obj[ lang ] ) { |
177 |
return obj[ lang ]; |
178 |
} |
179 |
} |
180 |
// Fallback language |
181 |
if ( obj[ fallback ] ) { |
182 |
return obj[ fallback ]; |
183 |
} |
184 |
// First existing language |
185 |
for ( lang in obj ) { |
186 |
return obj[ lang ]; |
187 |
} |
188 |
|
189 |
return undefined; |
190 |
}; |
191 |
|
192 |
/** |
193 |
* Check if a node is contained within another node |
194 |
* |
195 |
* Similar to jQuery#contains except a list of containers can be supplied |
196 |
* and a boolean argument allows you to include the container in the match list |
197 |
* |
198 |
* @param {HTMLElement|HTMLElement[]} containers Container node(s) to search in |
199 |
* @param {HTMLElement} contained Node to find |
200 |
* @param {boolean} [matchContainers] Include the container(s) in the list of nodes to match, otherwise only match descendants |
201 |
* @return {boolean} The node is in the list of target nodes |
202 |
*/ |
203 |
OO.ui.contains = function ( containers, contained, matchContainers ) { |
204 |
var i; |
205 |
if ( !Array.isArray( containers ) ) { |
206 |
containers = [ containers ]; |
207 |
} |
208 |
for ( i = containers.length - 1; i >= 0; i-- ) { |
209 |
if ( ( matchContainers && contained === containers[ i ] ) || $.contains( containers[ i ], contained ) ) { |
210 |
return true; |
211 |
} |
212 |
} |
213 |
return false; |
214 |
}; |
215 |
|
216 |
/** |
217 |
* Return a function, that, as long as it continues to be invoked, will not |
218 |
* be triggered. The function will be called after it stops being called for |
219 |
* N milliseconds. If `immediate` is passed, trigger the function on the |
220 |
* leading edge, instead of the trailing. |
221 |
* |
222 |
* Ported from: http://underscorejs.org/underscore.js |
223 |
* |
224 |
* @param {Function} func Function to debounce |
225 |
* @param {number} [wait=0] Wait period in milliseconds |
226 |
* @param {boolean} [immediate] Trigger on leading edge |
227 |
* @return {Function} Debounced function |
228 |
*/ |
229 |
OO.ui.debounce = function ( func, wait, immediate ) { |
230 |
var timeout; |
231 |
return function () { |
232 |
var context = this, |
233 |
args = arguments, |
234 |
later = function () { |
235 |
timeout = null; |
236 |
if ( !immediate ) { |
237 |
func.apply( context, args ); |
238 |
} |
239 |
}; |
240 |
if ( immediate && !timeout ) { |
241 |
func.apply( context, args ); |
242 |
} |
243 |
if ( !timeout || wait ) { |
244 |
clearTimeout( timeout ); |
245 |
timeout = setTimeout( later, wait ); |
246 |
} |
247 |
}; |
248 |
}; |
249 |
|
250 |
/** |
251 |
* Puts a console warning with provided message. |
252 |
* |
253 |
* @param {string} message Message |
254 |
*/ |
255 |
OO.ui.warnDeprecation = function ( message ) { |
256 |
if ( OO.getProp( window, 'console', 'warn' ) !== undefined ) { |
257 |
// eslint-disable-next-line no-console |
258 |
console.warn( message ); |
259 |
} |
260 |
}; |
261 |
|
262 |
/** |
263 |
* Returns a function, that, when invoked, will only be triggered at most once |
264 |
* during a given window of time. If called again during that window, it will |
265 |
* wait until the window ends and then trigger itself again. |
266 |
* |
267 |
* As it's not knowable to the caller whether the function will actually run |
268 |
* when the wrapper is called, return values from the function are entirely |
269 |
* discarded. |
270 |
* |
271 |
* @param {Function} func Function to throttle |
272 |
* @param {number} wait Throttle window length, in milliseconds |
273 |
* @return {Function} Throttled function |
274 |
*/ |
275 |
OO.ui.throttle = function ( func, wait ) { |
276 |
var context, args, timeout, |
277 |
previous = 0, |
278 |
run = function () { |
279 |
timeout = null; |
280 |
previous = OO.ui.now(); |
281 |
func.apply( context, args ); |
282 |
}; |
283 |
return function () { |
284 |
// Check how long it's been since the last time the function was |
285 |
// called, and whether it's more or less than the requested throttle |
286 |
// period. If it's less, run the function immediately. If it's more, |
287 |
// set a timeout for the remaining time -- but don't replace an |
288 |
// existing timeout, since that'd indefinitely prolong the wait. |
289 |
var remaining = wait - ( OO.ui.now() - previous ); |
290 |
context = this; |
291 |
args = arguments; |
292 |
if ( remaining <= 0 ) { |
293 |
// Note: unless wait was ridiculously large, this means we'll |
294 |
// automatically run the first time the function was called in a |
295 |
// given period. (If you provide a wait period larger than the |
296 |
// current Unix timestamp, you *deserve* unexpected behavior.) |
297 |
clearTimeout( timeout ); |
298 |
run(); |
299 |
} else if ( !timeout ) { |
300 |
timeout = setTimeout( run, remaining ); |
301 |
} |
302 |
}; |
303 |
}; |
304 |
|
305 |
/** |
306 |
* A (possibly faster) way to get the current timestamp as an integer |
307 |
* |
308 |
* @return {number} Current timestamp, in milliseconds since the Unix epoch |
309 |
*/ |
310 |
OO.ui.now = Date.now || function () { |
311 |
return new Date().getTime(); |
312 |
}; |
313 |
|
314 |
/** |
315 |
* Reconstitute a JavaScript object corresponding to a widget created by |
316 |
* the PHP implementation. |
317 |
* |
318 |
* This is an alias for `OO.ui.Element.static.infuse()`. |
319 |
* |
320 |
* @param {string|HTMLElement|jQuery} idOrNode |
321 |
* A DOM id (if a string) or node for the widget to infuse. |
322 |
* @param {Object} [config] Configuration options |
323 |
* @return {OO.ui.Element} |
324 |
* The `OO.ui.Element` corresponding to this (infusable) document node. |
325 |
*/ |
326 |
OO.ui.infuse = function ( idOrNode, config ) { |
327 |
return OO.ui.Element.static.infuse( idOrNode, config ); |
328 |
}; |
329 |
|
330 |
( function () { |
331 |
/** |
332 |
* Message store for the default implementation of OO.ui.msg |
333 |
* |
334 |
* Environments that provide a localization system should not use this, but should override |
335 |
* OO.ui.msg altogether. |
336 |
* |
337 |
* @private |
338 |
*/ |
339 |
var messages = { |
340 |
// Tool tip for a button that moves items in a list down one place |
341 |
'ooui-outline-control-move-down': 'Move item down', |
342 |
// Tool tip for a button that moves items in a list up one place |
343 |
'ooui-outline-control-move-up': 'Move item up', |
344 |
// Tool tip for a button that removes items from a list |
345 |
'ooui-outline-control-remove': 'Remove item', |
346 |
// Label for the toolbar group that contains a list of all other available tools |
347 |
'ooui-toolbar-more': 'More', |
348 |
// Label for the fake tool that expands the full list of tools in a toolbar group |
349 |
'ooui-toolgroup-expand': 'More', |
350 |
// Label for the fake tool that collapses the full list of tools in a toolbar group |
351 |
'ooui-toolgroup-collapse': 'Fewer', |
352 |
// Default label for the tooltip for the button that removes a tag item |
353 |
'ooui-item-remove': 'Remove', |
354 |
// Default label for the accept button of a confirmation dialog |
355 |
'ooui-dialog-message-accept': 'OK', |
356 |
// Default label for the reject button of a confirmation dialog |
357 |
'ooui-dialog-message-reject': 'Cancel', |
358 |
// Title for process dialog error description |
359 |
'ooui-dialog-process-error': 'Something went wrong', |
360 |
// Label for process dialog dismiss error button, visible when describing errors |
361 |
'ooui-dialog-process-dismiss': 'Dismiss', |
362 |
// Label for process dialog retry action button, visible when describing only recoverable errors |
363 |
'ooui-dialog-process-retry': 'Try again', |
364 |
// Label for process dialog retry action button, visible when describing only warnings |
365 |
'ooui-dialog-process-continue': 'Continue', |
366 |
// Label for the file selection widget's select file button |
367 |
'ooui-selectfile-button-select': 'Select a file', |
368 |
// Label for the file selection widget if file selection is not supported |
369 |
'ooui-selectfile-not-supported': 'File selection is not supported', |
370 |
// Label for the file selection widget when no file is currently selected |
371 |
'ooui-selectfile-placeholder': 'No file is selected', |
372 |
// Label for the file selection widget's drop target |
373 |
'ooui-selectfile-dragdrop-placeholder': 'Drop file here' |
374 |
}; |
375 |
|
376 |
/** |
377 |
* Get a localized message. |
378 |
* |
379 |
* After the message key, message parameters may optionally be passed. In the default implementation, |
380 |
* any occurrences of $1 are replaced with the first parameter, $2 with the second parameter, etc. |
381 |
* Alternative implementations of OO.ui.msg may use any substitution system they like, as long as |
382 |
* they support unnamed, ordered message parameters. |
383 |
* |
384 |
* In environments that provide a localization system, this function should be overridden to |
385 |
* return the message translated in the user's language. The default implementation always returns |
386 |
* English messages. An example of doing this with [jQuery.i18n](https://github.com/wikimedia/jquery.i18n) |
387 |
* follows. |
388 |
* |
389 |
* @example |
390 |
* var i, iLen, button, |
391 |
* messagePath = 'oojs-ui/dist/i18n/', |
392 |
* languages = [ $.i18n().locale, 'ur', 'en' ], |
393 |
* languageMap = {}; |
394 |
* |
395 |
* for ( i = 0, iLen = languages.length; i < iLen; i++ ) { |
396 |
* languageMap[ languages[ i ] ] = messagePath + languages[ i ].toLowerCase() + '.json'; |
397 |
* } |
398 |
* |
399 |
* $.i18n().load( languageMap ).done( function() { |
400 |
* // Replace the built-in `msg` only once we've loaded the internationalization. |
401 |
* // OOUI uses `OO.ui.deferMsg` for all initially-loaded messages. So long as |
402 |
* // you put off creating any widgets until this promise is complete, no English |
403 |
* // will be displayed. |
404 |
* OO.ui.msg = $.i18n; |
405 |
* |
406 |
* // A button displaying "OK" in the default locale |
407 |
* button = new OO.ui.ButtonWidget( { |
408 |
* label: OO.ui.msg( 'ooui-dialog-message-accept' ), |
409 |
* icon: 'check' |
410 |
* } ); |
411 |
* $( 'body' ).append( button.$element ); |
412 |
* |
413 |
* // A button displaying "OK" in Urdu |
414 |
* $.i18n().locale = 'ur'; |
415 |
* button = new OO.ui.ButtonWidget( { |
416 |
* label: OO.ui.msg( 'ooui-dialog-message-accept' ), |
417 |
* icon: 'check' |
418 |
* } ); |
419 |
* $( 'body' ).append( button.$element ); |
420 |
* } ); |
421 |
* |
422 |
* @param {string} key Message key |
423 |
* @param {...Mixed} [params] Message parameters |
424 |
* @return {string} Translated message with parameters substituted |
425 |
*/ |
426 |
OO.ui.msg = function ( key ) { |
427 |
var message = messages[ key ], |
428 |
params = Array.prototype.slice.call( arguments, 1 ); |
429 |
if ( typeof message === 'string' ) { |
430 |
// Perform $1 substitution |
431 |
message = message.replace( /\$(\d+)/g, function ( unused, n ) { |
432 |
var i = parseInt( n, 10 ); |
433 |
return params[ i - 1 ] !== undefined ? params[ i - 1 ] : '$' + n; |
434 |
} ); |
435 |
} else { |
436 |
// Return placeholder if message not found |
437 |
message = '[' + key + ']'; |
438 |
} |
439 |
return message; |
440 |
}; |
441 |
}() ); |
442 |
|
443 |
/** |
444 |
* Package a message and arguments for deferred resolution. |
445 |
* |
446 |
* Use this when you are statically specifying a message and the message may not yet be present. |
447 |
* |
448 |
* @param {string} key Message key |
449 |
* @param {...Mixed} [params] Message parameters |
450 |
* @return {Function} Function that returns the resolved message when executed |
451 |
*/ |
452 |
OO.ui.deferMsg = function () { |
453 |
var args = arguments; |
454 |
return function () { |
455 |
return OO.ui.msg.apply( OO.ui, args ); |
456 |
}; |
457 |
}; |
458 |
|
459 |
/** |
460 |
* Resolve a message. |
461 |
* |
462 |
* If the message is a function it will be executed, otherwise it will pass through directly. |
463 |
* |
464 |
* @param {Function|string} msg Deferred message, or message text |
465 |
* @return {string} Resolved message |
466 |
*/ |
467 |
OO.ui.resolveMsg = function ( msg ) { |
468 |
if ( $.isFunction( msg ) ) { |
469 |
return msg(); |
470 |
} |
471 |
return msg; |
472 |
}; |
473 |
|
474 |
/** |
475 |
* @param {string} url |
476 |
* @return {boolean} |
477 |
*/ |
478 |
OO.ui.isSafeUrl = function ( url ) { |
479 |
// Keep this function in sync with php/Tag.php |
480 |
var i, protocolWhitelist; |
481 |
|
482 |
function stringStartsWith( haystack, needle ) { |
483 |
return haystack.substr( 0, needle.length ) === needle; |
484 |
} |
485 |
|
486 |
protocolWhitelist = [ |
487 |
'bitcoin', 'ftp', 'ftps', 'geo', 'git', 'gopher', 'http', 'https', 'irc', 'ircs', |
488 |
'magnet', 'mailto', 'mms', 'news', 'nntp', 'redis', 'sftp', 'sip', 'sips', 'sms', 'ssh', |
489 |
'svn', 'tel', 'telnet', 'urn', 'worldwind', 'xmpp' |
490 |
]; |
491 |
|
492 |
if ( url === '' ) { |
493 |
return true; |
494 |
} |
495 |
|
496 |
for ( i = 0; i < protocolWhitelist.length; i++ ) { |
497 |
if ( stringStartsWith( url, protocolWhitelist[ i ] + ':' ) ) { |
498 |
return true; |
499 |
} |
500 |
} |
501 |
|
502 |
// This matches '//' too |
503 |
if ( stringStartsWith( url, '/' ) || stringStartsWith( url, './' ) ) { |
504 |
return true; |
505 |
} |
506 |
if ( stringStartsWith( url, '?' ) || stringStartsWith( url, '#' ) ) { |
507 |
return true; |
508 |
} |
509 |
|
510 |
return false; |
511 |
}; |
512 |
|
513 |
/** |
514 |
* Check if the user has a 'mobile' device. |
515 |
* |
516 |
* For our purposes this means the user is primarily using an |
517 |
* on-screen keyboard, touch input instead of a mouse and may |
518 |
* have a physically small display. |
519 |
* |
520 |
* It is left up to implementors to decide how to compute this |
521 |
* so the default implementation always returns false. |
522 |
* |
523 |
* @return {boolean} User is on a mobile device |
524 |
*/ |
525 |
OO.ui.isMobile = function () { |
526 |
return false; |
527 |
}; |
528 |
|
529 |
/** |
530 |
* Get the additional spacing that should be taken into account when displaying elements that are |
531 |
* clipped to the viewport, e.g. dropdown menus and popups. This is meant to be overridden to avoid |
532 |
* such menus overlapping any fixed headers/toolbars/navigation used by the site. |
533 |
* |
534 |
* @return {Object} Object with the properties 'top', 'right', 'bottom', 'left', each representing |
535 |
* the extra spacing from that edge of viewport (in pixels) |
536 |
*/ |
537 |
OO.ui.getViewportSpacing = function () { |
538 |
return { |
539 |
top: 0, |
540 |
right: 0, |
541 |
bottom: 0, |
542 |
left: 0 |
543 |
}; |
544 |
}; |
545 |
|
546 |
/** |
547 |
* Get the default overlay, which is used by various widgets when they are passed `$overlay: true`. |
548 |
* See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>. |
549 |
* |
550 |
* @return {jQuery} Default overlay node |
551 |
*/ |
552 |
OO.ui.getDefaultOverlay = function () { |
553 |
if ( !OO.ui.$defaultOverlay ) { |
Error |
Row 554, Column 27: "Prefer classList to $.addClass"
jquery/no-class
|
554 |
OO.ui.$defaultOverlay = $( '<div>' ).addClass( 'oo-ui-defaultOverlay' ); |
555 |
$( 'body' ).append( OO.ui.$defaultOverlay ); |
556 |
} |
557 |
return OO.ui.$defaultOverlay; |
558 |
}; |
559 |
|
|
|
/src/Dialog.js
|
0 problems
|
|
/src/dialogs/MessageDialog.js
|
0 problems
|
|
/src/dialogs/ProcessDialog.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 307, Column 11: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary |
3 |
* to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error |
4 |
* interface} alerts users to the trouble, permitting the user to dismiss the error and try again when |
5 |
* relevant. The ProcessDialog class is always extended and customized with the actions and content |
6 |
* required for each process. |
7 |
* |
8 |
* The process dialog box consists of a header that visually represents the ‘working’ state of long |
9 |
* processes with an animation. The header contains the dialog title as well as |
10 |
* two {@link OO.ui.ActionWidget action widgets}: a ‘safe’ action on the left (e.g., ‘Cancel’) and |
11 |
* a ‘primary’ action on the right (e.g., ‘Done’). |
12 |
* |
13 |
* Like other windows, the process dialog is managed by a {@link OO.ui.WindowManager window manager}. |
14 |
* Please see the [OOUI documentation on MediaWiki][1] for more information and examples. |
15 |
* |
16 |
* @example |
17 |
* // Example: Creating and opening a process dialog window. |
18 |
* function MyProcessDialog( config ) { |
19 |
* MyProcessDialog.parent.call( this, config ); |
20 |
* } |
21 |
* OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog ); |
22 |
* |
23 |
* MyProcessDialog.static.name = 'myProcessDialog'; |
24 |
* MyProcessDialog.static.title = 'Process dialog'; |
25 |
* MyProcessDialog.static.actions = [ |
26 |
* { action: 'save', label: 'Done', flags: 'primary' }, |
27 |
* { label: 'Cancel', flags: 'safe' } |
28 |
* ]; |
29 |
* |
30 |
* MyProcessDialog.prototype.initialize = function () { |
31 |
* MyProcessDialog.parent.prototype.initialize.apply( this, arguments ); |
32 |
* this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } ); |
33 |
* this.content.$element.append( '<p>This is a process dialog window. The header contains the title and two buttons: \'Cancel\' (a safe action) on the left and \'Done\' (a primary action) on the right.</p>' ); |
34 |
* this.$body.append( this.content.$element ); |
35 |
* }; |
36 |
* MyProcessDialog.prototype.getActionProcess = function ( action ) { |
37 |
* var dialog = this; |
38 |
* if ( action ) { |
39 |
* return new OO.ui.Process( function () { |
40 |
* dialog.close( { action: action } ); |
41 |
* } ); |
42 |
* } |
43 |
* return MyProcessDialog.parent.prototype.getActionProcess.call( this, action ); |
44 |
* }; |
45 |
* |
46 |
* var windowManager = new OO.ui.WindowManager(); |
47 |
* $( 'body' ).append( windowManager.$element ); |
48 |
* |
49 |
* var dialog = new MyProcessDialog(); |
50 |
* windowManager.addWindows( [ dialog ] ); |
51 |
* windowManager.openWindow( dialog ); |
52 |
* |
53 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs |
54 |
* |
55 |
* @abstract |
56 |
* @class |
57 |
* @extends OO.ui.Dialog |
58 |
* |
59 |
* @constructor |
60 |
* @param {Object} [config] Configuration options |
61 |
*/ |
62 |
OO.ui.ProcessDialog = function OoUiProcessDialog( config ) { |
63 |
// Parent constructor |
64 |
OO.ui.ProcessDialog.parent.call( this, config ); |
65 |
|
66 |
// Properties |
67 |
this.fitOnOpen = false; |
68 |
|
69 |
// Initialization |
70 |
this.$element.addClass( 'oo-ui-processDialog' ); |
71 |
}; |
72 |
|
73 |
/* Setup */ |
74 |
|
75 |
OO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog ); |
76 |
|
77 |
/* Methods */ |
78 |
|
79 |
/** |
80 |
* Handle dismiss button click events. |
81 |
* |
82 |
* Hides errors. |
83 |
* |
84 |
* @private |
85 |
*/ |
86 |
OO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () { |
87 |
this.hideErrors(); |
88 |
}; |
89 |
|
90 |
/** |
91 |
* Handle retry button click events. |
92 |
* |
93 |
* Hides errors and then tries again. |
94 |
* |
95 |
* @private |
96 |
*/ |
97 |
OO.ui.ProcessDialog.prototype.onRetryButtonClick = function () { |
98 |
this.hideErrors(); |
99 |
this.executeAction( this.currentAction ); |
100 |
}; |
101 |
|
102 |
/** |
103 |
* @inheritdoc |
104 |
*/ |
105 |
OO.ui.ProcessDialog.prototype.initialize = function () { |
106 |
// Parent method |
107 |
OO.ui.ProcessDialog.parent.prototype.initialize.call( this ); |
108 |
|
109 |
// Properties |
110 |
this.$navigation = $( '<div>' ); |
111 |
this.$location = $( '<div>' ); |
112 |
this.$safeActions = $( '<div>' ); |
113 |
this.$primaryActions = $( '<div>' ); |
114 |
this.$otherActions = $( '<div>' ); |
115 |
this.dismissButton = new OO.ui.ButtonWidget( { |
116 |
label: OO.ui.msg( 'ooui-dialog-process-dismiss' ) |
117 |
} ); |
118 |
this.retryButton = new OO.ui.ButtonWidget(); |
119 |
this.$errors = $( '<div>' ); |
120 |
this.$errorsTitle = $( '<div>' ); |
121 |
|
122 |
// Events |
123 |
this.dismissButton.connect( this, { click: 'onDismissErrorButtonClick' } ); |
124 |
this.retryButton.connect( this, { click: 'onRetryButtonClick' } ); |
125 |
|
126 |
// Initialization |
127 |
this.title.$element.addClass( 'oo-ui-processDialog-title' ); |
128 |
this.$location |
129 |
.append( this.title.$element ) |
130 |
.addClass( 'oo-ui-processDialog-location' ); |
131 |
this.$safeActions.addClass( 'oo-ui-processDialog-actions-safe' ); |
132 |
this.$primaryActions.addClass( 'oo-ui-processDialog-actions-primary' ); |
133 |
this.$otherActions.addClass( 'oo-ui-processDialog-actions-other' ); |
134 |
this.$errorsTitle |
135 |
.addClass( 'oo-ui-processDialog-errors-title' ) |
136 |
.text( OO.ui.msg( 'ooui-dialog-process-error' ) ); |
137 |
this.$errors |
138 |
.addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' ) |
139 |
.append( this.$errorsTitle, this.dismissButton.$element, this.retryButton.$element ); |
140 |
this.$content |
141 |
.addClass( 'oo-ui-processDialog-content' ) |
142 |
.append( this.$errors ); |
143 |
this.$navigation |
144 |
.addClass( 'oo-ui-processDialog-navigation' ) |
145 |
// Note: Order of appends below is important. These are in the order |
146 |
// we want tab to go through them. Display-order is handled entirely |
147 |
// by CSS absolute-positioning. As such, primary actions like "done" |
148 |
// should go first. |
149 |
.append( this.$primaryActions, this.$location, this.$safeActions ); |
150 |
this.$head.append( this.$navigation ); |
151 |
this.$foot.append( this.$otherActions ); |
152 |
}; |
153 |
|
154 |
/** |
155 |
* @inheritdoc |
156 |
*/ |
157 |
OO.ui.ProcessDialog.prototype.getActionWidgetConfig = function ( config ) { |
158 |
var isMobile = OO.ui.isMobile(); |
159 |
|
160 |
// Default to unframed on mobile |
161 |
config = $.extend( { framed: !isMobile }, config ); |
162 |
// Change back buttons to icon only on mobile |
163 |
if ( |
164 |
isMobile && |
165 |
( config.flags === 'back' || ( Array.isArray( config.flags ) && config.flags.indexOf( 'back' ) !== -1 ) ) |
166 |
) { |
167 |
$.extend( config, { |
168 |
icon: 'previous', |
169 |
label: '' |
170 |
} ); |
171 |
} |
172 |
|
173 |
return config; |
174 |
}; |
175 |
|
176 |
/** |
177 |
* @inheritdoc |
178 |
*/ |
179 |
OO.ui.ProcessDialog.prototype.attachActions = function () { |
180 |
var i, len, other, special, others; |
181 |
|
182 |
// Parent method |
183 |
OO.ui.ProcessDialog.parent.prototype.attachActions.call( this ); |
184 |
|
185 |
special = this.actions.getSpecial(); |
186 |
others = this.actions.getOthers(); |
187 |
if ( special.primary ) { |
188 |
this.$primaryActions.append( special.primary.$element ); |
189 |
} |
190 |
for ( i = 0, len = others.length; i < len; i++ ) { |
191 |
other = others[ i ]; |
192 |
this.$otherActions.append( other.$element ); |
193 |
} |
194 |
if ( special.safe ) { |
195 |
this.$safeActions.append( special.safe.$element ); |
196 |
} |
197 |
}; |
198 |
|
199 |
/** |
200 |
* @inheritdoc |
201 |
*/ |
202 |
OO.ui.ProcessDialog.prototype.executeAction = function ( action ) { |
203 |
var process = this; |
204 |
return OO.ui.ProcessDialog.parent.prototype.executeAction.call( this, action ) |
205 |
.fail( function ( errors ) { |
206 |
process.showErrors( errors || [] ); |
207 |
} ); |
208 |
}; |
209 |
|
210 |
/** |
211 |
* @inheritdoc |
212 |
*/ |
213 |
OO.ui.ProcessDialog.prototype.setDimensions = function () { |
214 |
var dialog = this; |
215 |
|
216 |
// Parent method |
217 |
OO.ui.ProcessDialog.parent.prototype.setDimensions.apply( this, arguments ); |
218 |
|
219 |
this.fitLabel(); |
220 |
|
221 |
// If there are many actions, they might be shown on multiple lines. Their layout can change when |
222 |
// resizing the dialog and when changing the actions. Adjust the height of the footer to fit them. |
223 |
dialog.$body.css( 'bottom', dialog.$foot.outerHeight( true ) ); |
224 |
// Wait for CSS transition to finish and do it again :( |
225 |
setTimeout( function () { |
226 |
dialog.$body.css( 'bottom', dialog.$foot.outerHeight( true ) ); |
227 |
}, 300 ); |
228 |
}; |
229 |
|
230 |
/** |
231 |
* Fit label between actions. |
232 |
* |
233 |
* @private |
234 |
* @chainable |
235 |
*/ |
236 |
OO.ui.ProcessDialog.prototype.fitLabel = function () { |
237 |
var safeWidth, primaryWidth, biggerWidth, labelWidth, navigationWidth, leftWidth, rightWidth, |
238 |
size = this.getSizeProperties(); |
239 |
|
240 |
if ( typeof size.width !== 'number' ) { |
241 |
if ( this.isOpened() ) { |
242 |
navigationWidth = this.$head.width() - 20; |
243 |
} else if ( this.isOpening() ) { |
244 |
if ( !this.fitOnOpen ) { |
245 |
// Size is relative and the dialog isn't open yet, so wait. |
246 |
// FIXME: This should ideally be handled by setup somehow. |
247 |
this.manager.lifecycle.opened.done( this.fitLabel.bind( this ) ); |
248 |
this.fitOnOpen = true; |
249 |
} |
250 |
return; |
251 |
} else { |
252 |
return; |
253 |
} |
254 |
} else { |
255 |
navigationWidth = size.width - 20; |
256 |
} |
257 |
|
258 |
safeWidth = this.$safeActions.is( ':visible' ) ? this.$safeActions.width() : 0; |
259 |
primaryWidth = this.$primaryActions.is( ':visible' ) ? this.$primaryActions.width() : 0; |
260 |
biggerWidth = Math.max( safeWidth, primaryWidth ); |
261 |
|
262 |
labelWidth = this.title.$element.width(); |
263 |
|
264 |
if ( 2 * biggerWidth + labelWidth < navigationWidth ) { |
265 |
// We have enough space to center the label |
266 |
leftWidth = rightWidth = biggerWidth; |
267 |
} else { |
268 |
// Let's hope we at least have enough space not to overlap, because we can't wrap the label… |
269 |
if ( this.getDir() === 'ltr' ) { |
270 |
leftWidth = safeWidth; |
271 |
rightWidth = primaryWidth; |
272 |
} else { |
273 |
leftWidth = primaryWidth; |
274 |
rightWidth = safeWidth; |
275 |
} |
276 |
} |
277 |
|
278 |
this.$location.css( { paddingLeft: leftWidth, paddingRight: rightWidth } ); |
279 |
|
280 |
return this; |
281 |
}; |
282 |
|
283 |
/** |
284 |
* Handle errors that occurred during accept or reject processes. |
285 |
* |
286 |
* @private |
287 |
* @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled |
288 |
*/ |
289 |
OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) { |
290 |
var i, len, $item, actions, |
291 |
items = [], |
292 |
abilities = {}, |
293 |
recoverable = true, |
294 |
warning = false; |
295 |
|
296 |
if ( errors instanceof OO.ui.Error ) { |
297 |
errors = [ errors ]; |
298 |
} |
299 |
|
300 |
for ( i = 0, len = errors.length; i < len; i++ ) { |
301 |
if ( !errors[ i ].isRecoverable() ) { |
302 |
recoverable = false; |
303 |
} |
304 |
if ( errors[ i ].isWarning() ) { |
305 |
warning = true; |
306 |
} |
Error |
Row 307, Column 11: "Prefer classList to $.addClass"
jquery/no-class
|
307 |
$item = $( '<div>' ) |
308 |
.addClass( 'oo-ui-processDialog-error' ) |
309 |
.append( errors[ i ].getMessage() ); |
310 |
items.push( $item[ 0 ] ); |
311 |
} |
312 |
this.$errorItems = $( items ); |
313 |
if ( recoverable ) { |
314 |
abilities[ this.currentAction ] = true; |
315 |
// Copy the flags from the first matching action |
316 |
actions = this.actions.get( { actions: this.currentAction } ); |
317 |
if ( actions.length ) { |
318 |
this.retryButton.clearFlags().setFlags( actions[ 0 ].getFlags() ); |
319 |
} |
320 |
} else { |
321 |
abilities[ this.currentAction ] = false; |
322 |
this.actions.setAbilities( abilities ); |
323 |
} |
324 |
if ( warning ) { |
325 |
this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) ); |
326 |
} else { |
327 |
this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) ); |
328 |
} |
329 |
this.retryButton.toggle( recoverable ); |
330 |
this.$errorsTitle.after( this.$errorItems ); |
331 |
this.$errors.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 ); |
332 |
}; |
333 |
|
334 |
/** |
335 |
* Hide errors. |
336 |
* |
337 |
* @private |
338 |
*/ |
339 |
OO.ui.ProcessDialog.prototype.hideErrors = function () { |
340 |
this.$errors.addClass( 'oo-ui-element-hidden' ); |
341 |
if ( this.$errorItems ) { |
342 |
this.$errorItems.remove(); |
343 |
this.$errorItems = null; |
344 |
} |
345 |
}; |
346 |
|
347 |
/** |
348 |
* @inheritdoc |
349 |
*/ |
350 |
OO.ui.ProcessDialog.prototype.getTeardownProcess = function ( data ) { |
351 |
// Parent method |
352 |
return OO.ui.ProcessDialog.parent.prototype.getTeardownProcess.call( this, data ) |
353 |
.first( function () { |
354 |
// Make sure to hide errors |
355 |
this.hideErrors(); |
356 |
this.fitOnOpen = false; |
357 |
}, this ); |
358 |
}; |
359 |
|
|
|
/src/Element.js
|
20 problems (20 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 138, Column 8: "Prefer getAttribute to $.attr"
jquery/no-attr
|
Error |
Row 153, Column 9: "Prefer WeakMap to $.data"
jquery/no-data
|
Error |
Row 164, Column 22: "Prefer WeakMap to $.data"
jquery/no-data
|
Error |
Row 174, Column 9: "Prefer getAttribute to $.attr"
jquery/no-attr
|
Error |
Row 213, Column 9: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
Error |
Row 216, Column 2: "Prefer WeakMap to $.data"
jquery/no-data
|
Error |
Row 375, Column 9: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 469, Column 52: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 470, Column 54: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 471, Column 58: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 472, Column 56: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 577, Column 33: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 645, Column 13: "Prefer parentElement to $.parent"
jquery/no-parent
|
Error |
Row 653, Column 7: "Prefer matches to $.is"
jquery/no-is
|
Error |
Row 663, Column 10: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 673, Column 13: "Prefer parentElement to $.parent"
jquery/no-parent
|
Error |
Row 692, Column 14: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
Error |
Row 705, Column 7: "Prefer matches to $.is"
jquery/no-is
|
Error |
Row 738, Column 3: "$.animate is not allowed"
jquery/no-animate
|
Error |
Row 968, Column 10: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
Line |
Source |
1 |
/** |
2 |
* Each Element represents a rendering in the DOM—a button or an icon, for example, or anything |
3 |
* that is visible to a user. Unlike {@link OO.ui.Widget widgets}, plain elements usually do not have events |
4 |
* connected to them and can't be interacted with. |
5 |
* |
6 |
* @abstract |
7 |
* @class |
8 |
* |
9 |
* @constructor |
10 |
* @param {Object} [config] Configuration options |
11 |
* @cfg {string[]} [classes] The names of the CSS classes to apply to the element. CSS styles are added |
12 |
* to the top level (e.g., the outermost div) of the element. See the [OOUI documentation on MediaWiki][2] |
13 |
* for an example. |
14 |
* [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Buttons_and_Switches#cssExample |
15 |
* @cfg {string} [id] The HTML id attribute used in the rendered tag. |
16 |
* @cfg {string} [text] Text to insert |
17 |
* @cfg {Array} [content] An array of content elements to append (after #text). |
18 |
* Strings will be html-escaped; use an OO.ui.HtmlSnippet to append raw HTML. |
19 |
* Instances of OO.ui.Element will have their $element appended. |
20 |
* @cfg {jQuery} [$content] Content elements to append (after #text). |
21 |
* @cfg {jQuery} [$element] Wrapper element. Defaults to a new element with #getTagName. |
22 |
* @cfg {Mixed} [data] Custom data of any type or combination of types (e.g., string, number, array, object). |
23 |
* Data can also be specified with the #setData method. |
24 |
*/ |
25 |
OO.ui.Element = function OoUiElement( config ) { |
26 |
if ( OO.ui.isDemo ) { |
27 |
this.initialConfig = config; |
28 |
} |
29 |
// Configuration initialization |
30 |
config = config || {}; |
31 |
|
32 |
// Properties |
33 |
this.$ = $; |
34 |
this.elementId = null; |
35 |
this.visible = true; |
36 |
this.data = config.data; |
37 |
this.$element = config.$element || |
38 |
$( document.createElement( this.getTagName() ) ); |
39 |
this.elementGroup = null; |
40 |
|
41 |
// Initialization |
42 |
if ( Array.isArray( config.classes ) ) { |
43 |
this.$element.addClass( config.classes ); |
44 |
} |
45 |
if ( config.id ) { |
46 |
this.setElementId( config.id ); |
47 |
} |
48 |
if ( config.text ) { |
49 |
this.$element.text( config.text ); |
50 |
} |
51 |
if ( config.content ) { |
52 |
// The `content` property treats plain strings as text; use an |
53 |
// HtmlSnippet to append HTML content. `OO.ui.Element`s get their |
54 |
// appropriate $element appended. |
55 |
this.$element.append( config.content.map( function ( v ) { |
56 |
if ( typeof v === 'string' ) { |
57 |
// Escape string so it is properly represented in HTML. |
58 |
return document.createTextNode( v ); |
59 |
} else if ( v instanceof OO.ui.HtmlSnippet ) { |
60 |
// Bypass escaping. |
61 |
return v.toString(); |
62 |
} else if ( v instanceof OO.ui.Element ) { |
63 |
return v.$element; |
64 |
} |
65 |
return v; |
66 |
} ) ); |
67 |
} |
68 |
if ( config.$content ) { |
69 |
// The `$content` property treats plain strings as HTML. |
70 |
this.$element.append( config.$content ); |
71 |
} |
72 |
}; |
73 |
|
74 |
/* Setup */ |
75 |
|
76 |
OO.initClass( OO.ui.Element ); |
77 |
|
78 |
/* Static Properties */ |
79 |
|
80 |
/** |
81 |
* The name of the HTML tag used by the element. |
82 |
* |
83 |
* The static value may be ignored if the #getTagName method is overridden. |
84 |
* |
85 |
* @static |
86 |
* @inheritable |
87 |
* @property {string} |
88 |
*/ |
89 |
OO.ui.Element.static.tagName = 'div'; |
90 |
|
91 |
/* Static Methods */ |
92 |
|
93 |
/** |
94 |
* Reconstitute a JavaScript object corresponding to a widget created |
95 |
* by the PHP implementation. |
96 |
* |
97 |
* @param {string|HTMLElement|jQuery} idOrNode |
98 |
* A DOM id (if a string) or node for the widget to infuse. |
99 |
* @param {Object} [config] Configuration options |
100 |
* @return {OO.ui.Element} |
101 |
* The `OO.ui.Element` corresponding to this (infusable) document node. |
102 |
* For `Tag` objects emitted on the HTML side (used occasionally for content) |
103 |
* the value returned is a newly-created Element wrapping around the existing |
104 |
* DOM node. |
105 |
*/ |
106 |
OO.ui.Element.static.infuse = function ( idOrNode, config ) { |
107 |
var obj = OO.ui.Element.static.unsafeInfuse( idOrNode, config, false ); |
108 |
// Verify that the type matches up. |
109 |
// FIXME: uncomment after T89721 is fixed, see T90929. |
110 |
/* |
111 |
if ( !( obj instanceof this['class'] ) ) { |
112 |
throw new Error( 'Infusion type mismatch!' ); |
113 |
} |
114 |
*/ |
115 |
return obj; |
116 |
}; |
117 |
|
118 |
/** |
119 |
* Implementation helper for `infuse`; skips the type check and has an |
120 |
* extra property so that only the top-level invocation touches the DOM. |
121 |
* |
122 |
* @private |
123 |
* @param {string|HTMLElement|jQuery} idOrNode |
124 |
* @param {Object} [config] Configuration options |
125 |
* @param {jQuery.Promise} [domPromise] A promise that will be resolved |
126 |
* when the top-level widget of this infusion is inserted into DOM, |
127 |
* replacing the original node; only used internally. |
128 |
* @return {OO.ui.Element} |
129 |
*/ |
130 |
OO.ui.Element.static.unsafeInfuse = function ( idOrNode, config, domPromise ) { |
131 |
// look for a cached result of a previous infusion. |
132 |
var id, $elem, error, data, cls, parts, parent, obj, top, state, infusedChildren; |
133 |
if ( typeof idOrNode === 'string' ) { |
134 |
id = idOrNode; |
135 |
$elem = $( document.getElementById( id ) ); |
136 |
} else { |
137 |
$elem = $( idOrNode ); |
Error |
Row 138, Column 8: "Prefer getAttribute to $.attr"
jquery/no-attr
|
138 |
id = $elem.attr( 'id' ); |
139 |
} |
140 |
if ( !$elem.length ) { |
141 |
if ( typeof idOrNode === 'string' ) { |
142 |
error = 'Widget not found: ' + idOrNode; |
143 |
} else if ( idOrNode && idOrNode.selector ) { |
144 |
error = 'Widget not found: ' + idOrNode.selector; |
145 |
} else { |
146 |
error = 'Widget not found'; |
147 |
} |
148 |
throw new Error( error ); |
149 |
} |
150 |
if ( $elem[ 0 ].oouiInfused ) { |
151 |
$elem = $elem[ 0 ].oouiInfused; |
152 |
} |
Error |
Row 153, Column 9: "Prefer WeakMap to $.data"
jquery/no-data
|
153 |
data = $elem.data( 'ooui-infused' ); |
154 |
if ( data ) { |
155 |
// cached! |
156 |
if ( data === true ) { |
157 |
throw new Error( 'Circular dependency! ' + id ); |
158 |
} |
159 |
if ( domPromise ) { |
160 |
// pick up dynamic state, like focus, value of form inputs, scroll position, etc. |
161 |
state = data.constructor.static.gatherPreInfuseState( $elem, data ); |
162 |
// restore dynamic state after the new element is re-inserted into DOM under infused parent |
163 |
domPromise.done( data.restorePreInfuseState.bind( data, state ) ); |
Error |
Row 164, Column 22: "Prefer WeakMap to $.data"
jquery/no-data
|
164 |
infusedChildren = $elem.data( 'ooui-infused-children' ); |
165 |
if ( infusedChildren && infusedChildren.length ) { |
166 |
infusedChildren.forEach( function ( data ) { |
167 |
var state = data.constructor.static.gatherPreInfuseState( $elem, data ); |
168 |
domPromise.done( data.restorePreInfuseState.bind( data, state ) ); |
169 |
} ); |
170 |
} |
171 |
} |
172 |
return data; |
173 |
} |
Error |
Row 174, Column 9: "Prefer getAttribute to $.attr"
jquery/no-attr
|
174 |
data = $elem.attr( 'data-ooui' ); |
175 |
if ( !data ) { |
176 |
throw new Error( 'No infusion data found: ' + id ); |
177 |
} |
178 |
try { |
179 |
data = JSON.parse( data ); |
180 |
} catch ( _ ) { |
181 |
data = null; |
182 |
} |
183 |
if ( !( data && data._ ) ) { |
184 |
throw new Error( 'No valid infusion data found: ' + id ); |
185 |
} |
186 |
if ( data._ === 'Tag' ) { |
187 |
// Special case: this is a raw Tag; wrap existing node, don't rebuild. |
188 |
return new OO.ui.Element( $.extend( {}, config, { $element: $elem } ) ); |
189 |
} |
190 |
parts = data._.split( '.' ); |
191 |
cls = OO.getProp.apply( OO, [ window ].concat( parts ) ); |
192 |
if ( cls === undefined ) { |
193 |
throw new Error( 'Unknown widget type: id: ' + id + ', class: ' + data._ ); |
194 |
} |
195 |
|
196 |
// Verify that we're creating an OO.ui.Element instance |
197 |
parent = cls.parent; |
198 |
|
199 |
while ( parent !== undefined ) { |
200 |
if ( parent === OO.ui.Element ) { |
201 |
// Safe |
202 |
break; |
203 |
} |
204 |
|
205 |
parent = parent.parent; |
206 |
} |
207 |
|
208 |
if ( parent !== OO.ui.Element ) { |
209 |
throw new Error( 'Unknown widget type: id: ' + id + ', class: ' + data._ ); |
210 |
} |
211 |
|
212 |
if ( !domPromise ) { |
Error |
Row 213, Column 9: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
213 |
top = $.Deferred(); |
214 |
domPromise = top.promise(); |
215 |
} |
Error |
Row 216, Column 2: "Prefer WeakMap to $.data"
jquery/no-data
|
216 |
$elem.data( 'ooui-infused', true ); // prevent loops |
217 |
data.id = id; // implicit |
218 |
infusedChildren = []; |
219 |
data = OO.copy( data, null, function deserialize( value ) { |
220 |
var infused; |
221 |
if ( OO.isPlainObject( value ) ) { |
222 |
if ( value.tag ) { |
223 |
infused = OO.ui.Element.static.unsafeInfuse( value.tag, config, domPromise ); |
224 |
infusedChildren.push( infused ); |
225 |
// Flatten the structure |
226 |
infusedChildren.push.apply( infusedChildren, infused.$element.data( 'ooui-infused-children' ) || [] ); |
227 |
infused.$element.removeData( 'ooui-infused-children' ); |
228 |
return infused; |
229 |
} |
230 |
if ( value.html !== undefined ) { |
231 |
return new OO.ui.HtmlSnippet( value.html ); |
232 |
} |
233 |
} |
234 |
} ); |
235 |
// allow widgets to reuse parts of the DOM |
236 |
data = cls.static.reusePreInfuseDOM( $elem[ 0 ], data ); |
237 |
// pick up dynamic state, like focus, value of form inputs, scroll position, etc. |
238 |
state = cls.static.gatherPreInfuseState( $elem[ 0 ], data ); |
239 |
// rebuild widget |
240 |
// eslint-disable-next-line new-cap |
241 |
obj = new cls( $.extend( {}, config, data ) ); |
242 |
// If anyone is holding a reference to the old DOM element, |
243 |
// let's allow them to OO.ui.infuse() it and do what they expect, see T105828. |
244 |
// Do not use jQuery.data(), as using it on detached nodes leaks memory in 1.x line by design. |
245 |
$elem[ 0 ].oouiInfused = obj.$element; |
246 |
// now replace old DOM with this new DOM. |
247 |
if ( top ) { |
248 |
// An efficient constructor might be able to reuse the entire DOM tree of the original element, |
249 |
// so only mutate the DOM if we need to. |
250 |
if ( $elem[ 0 ] !== obj.$element[ 0 ] ) { |
251 |
$elem.replaceWith( obj.$element ); |
252 |
} |
253 |
top.resolve(); |
254 |
} |
255 |
obj.$element.data( 'ooui-infused', obj ); |
256 |
obj.$element.data( 'ooui-infused-children', infusedChildren ); |
257 |
// set the 'data-ooui' attribute so we can identify infused widgets |
258 |
obj.$element.attr( 'data-ooui', '' ); |
259 |
// restore dynamic state after the new element is inserted into DOM |
260 |
domPromise.done( obj.restorePreInfuseState.bind( obj, state ) ); |
261 |
return obj; |
262 |
}; |
263 |
|
264 |
/** |
265 |
* Pick out parts of `node`'s DOM to be reused when infusing a widget. |
266 |
* |
267 |
* This method **must not** make any changes to the DOM, only find interesting pieces and add them |
268 |
* to `config` (which should then be returned). Actual DOM juggling should then be done by the |
269 |
* constructor, which will be given the enhanced config. |
270 |
* |
271 |
* @protected |
272 |
* @param {HTMLElement} node |
273 |
* @param {Object} config |
274 |
* @return {Object} |
275 |
*/ |
276 |
OO.ui.Element.static.reusePreInfuseDOM = function ( node, config ) { |
277 |
return config; |
278 |
}; |
279 |
|
280 |
/** |
281 |
* Gather the dynamic state (focus, value of form inputs, scroll position, etc.) of an HTML DOM node |
282 |
* (and its children) that represent an Element of the same class and the given configuration, |
283 |
* generated by the PHP implementation. |
284 |
* |
285 |
* This method is called just before `node` is detached from the DOM. The return value of this |
286 |
* function will be passed to #restorePreInfuseState after the newly created widget's #$element |
287 |
* is inserted into DOM to replace `node`. |
288 |
* |
289 |
* @protected |
290 |
* @param {HTMLElement} node |
291 |
* @param {Object} config |
292 |
* @return {Object} |
293 |
*/ |
294 |
OO.ui.Element.static.gatherPreInfuseState = function () { |
295 |
return {}; |
296 |
}; |
297 |
|
298 |
/** |
299 |
* Get a jQuery function within a specific document. |
300 |
* |
301 |
* @static |
302 |
* @param {jQuery|HTMLElement|HTMLDocument|Window} context Context to bind the function to |
303 |
* @param {jQuery} [$iframe] HTML iframe element that contains the document, omit if document is |
304 |
* not in an iframe |
305 |
* @return {Function} Bound jQuery function |
306 |
*/ |
307 |
OO.ui.Element.static.getJQuery = function ( context, $iframe ) { |
308 |
function wrapper( selector ) { |
309 |
return $( selector, wrapper.context ); |
310 |
} |
311 |
|
312 |
wrapper.context = this.getDocument( context ); |
313 |
|
314 |
if ( $iframe ) { |
315 |
wrapper.$iframe = $iframe; |
316 |
} |
317 |
|
318 |
return wrapper; |
319 |
}; |
320 |
|
321 |
/** |
322 |
* Get the document of an element. |
323 |
* |
324 |
* @static |
325 |
* @param {jQuery|HTMLElement|HTMLDocument|Window} obj Object to get the document for |
326 |
* @return {HTMLDocument|null} Document object |
327 |
*/ |
328 |
OO.ui.Element.static.getDocument = function ( obj ) { |
329 |
// jQuery - selections created "offscreen" won't have a context, so .context isn't reliable |
330 |
return ( obj[ 0 ] && obj[ 0 ].ownerDocument ) || |
331 |
// Empty jQuery selections might have a context |
332 |
obj.context || |
333 |
// HTMLElement |
334 |
obj.ownerDocument || |
335 |
// Window |
336 |
obj.document || |
337 |
// HTMLDocument |
338 |
( obj.nodeType === Node.DOCUMENT_NODE && obj ) || |
339 |
null; |
340 |
}; |
341 |
|
342 |
/** |
343 |
* Get the window of an element or document. |
344 |
* |
345 |
* @static |
346 |
* @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the window for |
347 |
* @return {Window} Window object |
348 |
*/ |
349 |
OO.ui.Element.static.getWindow = function ( obj ) { |
350 |
var doc = this.getDocument( obj ); |
351 |
return doc.defaultView; |
352 |
}; |
353 |
|
354 |
/** |
355 |
* Get the direction of an element or document. |
356 |
* |
357 |
* @static |
358 |
* @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the direction for |
359 |
* @return {string} Text direction, either 'ltr' or 'rtl' |
360 |
*/ |
361 |
OO.ui.Element.static.getDir = function ( obj ) { |
362 |
var isDoc, isWin; |
363 |
|
364 |
if ( obj instanceof jQuery ) { |
365 |
obj = obj[ 0 ]; |
366 |
} |
367 |
isDoc = obj.nodeType === Node.DOCUMENT_NODE; |
368 |
isWin = obj.document !== undefined; |
369 |
if ( isDoc || isWin ) { |
370 |
if ( isWin ) { |
371 |
obj = obj.document; |
372 |
} |
373 |
obj = obj.body; |
374 |
} |
Error |
Row 375, Column 9: "Prefer getComputedStyle to $.css"
jquery/no-css
|
375 |
return $( obj ).css( 'direction' ); |
376 |
}; |
377 |
|
378 |
/** |
379 |
* Get the offset between two frames. |
380 |
* |
381 |
* TODO: Make this function not use recursion. |
382 |
* |
383 |
* @static |
384 |
* @param {Window} from Window of the child frame |
385 |
* @param {Window} [to=window] Window of the parent frame |
386 |
* @param {Object} [offset] Offset to start with, used internally |
387 |
* @return {Object} Offset object, containing left and top properties |
388 |
*/ |
389 |
OO.ui.Element.static.getFrameOffset = function ( from, to, offset ) { |
390 |
var i, len, frames, frame, rect; |
391 |
|
392 |
if ( !to ) { |
393 |
to = window; |
394 |
} |
395 |
if ( !offset ) { |
396 |
offset = { top: 0, left: 0 }; |
397 |
} |
398 |
if ( from.parent === from ) { |
399 |
return offset; |
400 |
} |
401 |
|
402 |
// Get iframe element |
403 |
frames = from.parent.document.getElementsByTagName( 'iframe' ); |
404 |
for ( i = 0, len = frames.length; i < len; i++ ) { |
405 |
if ( frames[ i ].contentWindow === from ) { |
406 |
frame = frames[ i ]; |
407 |
break; |
408 |
} |
409 |
} |
410 |
|
411 |
// Recursively accumulate offset values |
412 |
if ( frame ) { |
413 |
rect = frame.getBoundingClientRect(); |
414 |
offset.left += rect.left; |
415 |
offset.top += rect.top; |
416 |
if ( from !== to ) { |
417 |
this.getFrameOffset( from.parent, offset ); |
418 |
} |
419 |
} |
420 |
return offset; |
421 |
}; |
422 |
|
423 |
/** |
424 |
* Get the offset between two elements. |
425 |
* |
426 |
* The two elements may be in a different frame, but in that case the frame $element is in must |
427 |
* be contained in the frame $anchor is in. |
428 |
* |
429 |
* @static |
430 |
* @param {jQuery} $element Element whose position to get |
431 |
* @param {jQuery} $anchor Element to get $element's position relative to |
432 |
* @return {Object} Translated position coordinates, containing top and left properties |
433 |
*/ |
434 |
OO.ui.Element.static.getRelativePosition = function ( $element, $anchor ) { |
435 |
var iframe, iframePos, |
436 |
pos = $element.offset(), |
437 |
anchorPos = $anchor.offset(), |
438 |
elementDocument = this.getDocument( $element ), |
439 |
anchorDocument = this.getDocument( $anchor ); |
440 |
|
441 |
// If $element isn't in the same document as $anchor, traverse up |
442 |
while ( elementDocument !== anchorDocument ) { |
443 |
iframe = elementDocument.defaultView.frameElement; |
444 |
if ( !iframe ) { |
445 |
throw new Error( '$element frame is not contained in $anchor frame' ); |
446 |
} |
447 |
iframePos = $( iframe ).offset(); |
448 |
pos.left += iframePos.left; |
449 |
pos.top += iframePos.top; |
450 |
elementDocument = iframe.ownerDocument; |
451 |
} |
452 |
pos.left -= anchorPos.left; |
453 |
pos.top -= anchorPos.top; |
454 |
return pos; |
455 |
}; |
456 |
|
457 |
/** |
458 |
* Get element border sizes. |
459 |
* |
460 |
* @static |
461 |
* @param {HTMLElement} el Element to measure |
462 |
* @return {Object} Dimensions object with `top`, `left`, `bottom` and `right` properties |
463 |
*/ |
464 |
OO.ui.Element.static.getBorders = function ( el ) { |
465 |
var doc = el.ownerDocument, |
466 |
win = doc.defaultView, |
467 |
style = win.getComputedStyle( el, null ), |
468 |
$el = $( el ), |
Error |
Row 469, Column 52: "Prefer getComputedStyle to $.css"
jquery/no-css
|
469 |
top = parseFloat( style ? style.borderTopWidth : $el.css( 'borderTopWidth' ) ) || 0, |
Error |
Row 470, Column 54: "Prefer getComputedStyle to $.css"
jquery/no-css
|
470 |
left = parseFloat( style ? style.borderLeftWidth : $el.css( 'borderLeftWidth' ) ) || 0, |
Error |
Row 471, Column 58: "Prefer getComputedStyle to $.css"
jquery/no-css
|
471 |
bottom = parseFloat( style ? style.borderBottomWidth : $el.css( 'borderBottomWidth' ) ) || 0, |
Error |
Row 472, Column 56: "Prefer getComputedStyle to $.css"
jquery/no-css
|
472 |
right = parseFloat( style ? style.borderRightWidth : $el.css( 'borderRightWidth' ) ) || 0; |
473 |
|
474 |
return { |
475 |
top: top, |
476 |
left: left, |
477 |
bottom: bottom, |
478 |
right: right |
479 |
}; |
480 |
}; |
481 |
|
482 |
/** |
483 |
* Get dimensions of an element or window. |
484 |
* |
485 |
* @static |
486 |
* @param {HTMLElement|Window} el Element to measure |
487 |
* @return {Object} Dimensions object with `borders`, `scroll`, `scrollbar` and `rect` properties |
488 |
*/ |
489 |
OO.ui.Element.static.getDimensions = function ( el ) { |
490 |
var $el, $win, |
491 |
doc = el.ownerDocument || el.document, |
492 |
win = doc.defaultView; |
493 |
|
494 |
if ( win === el || el === doc.documentElement ) { |
495 |
$win = $( win ); |
496 |
return { |
497 |
borders: { top: 0, left: 0, bottom: 0, right: 0 }, |
498 |
scroll: { |
499 |
top: $win.scrollTop(), |
500 |
left: $win.scrollLeft() |
501 |
}, |
502 |
scrollbar: { right: 0, bottom: 0 }, |
503 |
rect: { |
504 |
top: 0, |
505 |
left: 0, |
506 |
bottom: $win.innerHeight(), |
507 |
right: $win.innerWidth() |
508 |
} |
509 |
}; |
510 |
} else { |
511 |
$el = $( el ); |
512 |
return { |
513 |
borders: this.getBorders( el ), |
514 |
scroll: { |
515 |
top: $el.scrollTop(), |
516 |
left: $el.scrollLeft() |
517 |
}, |
518 |
scrollbar: { |
519 |
right: $el.innerWidth() - el.clientWidth, |
520 |
bottom: $el.innerHeight() - el.clientHeight |
521 |
}, |
522 |
rect: el.getBoundingClientRect() |
523 |
}; |
524 |
} |
525 |
}; |
526 |
|
527 |
/** |
528 |
* Get the number of pixels that an element's content is scrolled to the left. |
529 |
* |
530 |
* Adapted from <https://github.com/othree/jquery.rtl-scroll-type>. |
531 |
* Original code copyright 2012 Wei-Ko Kao, licensed under the MIT License. |
532 |
* |
533 |
* This function smooths out browser inconsistencies (nicely described in the README at |
534 |
* <https://github.com/othree/jquery.rtl-scroll-type>) and produces a result consistent |
535 |
* with Firefox's 'scrollLeft', which seems the sanest. |
536 |
* |
537 |
* @static |
538 |
* @method |
539 |
* @param {HTMLElement|Window} el Element to measure |
540 |
* @return {number} Scroll position from the left. |
541 |
* If the element's direction is LTR, this is a positive number between `0` (initial scroll position) |
542 |
* and `el.scrollWidth - el.clientWidth` (furthest possible scroll position). |
543 |
* If the element's direction is RTL, this is a negative number between `0` (initial scroll position) |
544 |
* and `-el.scrollWidth + el.clientWidth` (furthest possible scroll position). |
545 |
*/ |
546 |
OO.ui.Element.static.getScrollLeft = ( function () { |
547 |
var rtlScrollType = null; |
548 |
|
549 |
function test() { |
550 |
var $definer = $( '<div dir="rtl" style="font-size: 14px; width: 1px; height: 1px; position: absolute; top: -1000px; overflow: scroll">A</div>' ), |
551 |
definer = $definer[ 0 ]; |
552 |
|
553 |
$definer.appendTo( 'body' ); |
554 |
if ( definer.scrollLeft > 0 ) { |
555 |
// Safari, Chrome |
556 |
rtlScrollType = 'default'; |
557 |
} else { |
558 |
definer.scrollLeft = 1; |
559 |
if ( definer.scrollLeft === 0 ) { |
560 |
// Firefox, old Opera |
561 |
rtlScrollType = 'negative'; |
562 |
} else { |
563 |
// Internet Explorer, Edge |
564 |
rtlScrollType = 'reverse'; |
565 |
} |
566 |
} |
567 |
$definer.remove(); |
568 |
} |
569 |
|
570 |
return function getScrollLeft( el ) { |
571 |
var isRoot = el.window === el || |
572 |
el === el.ownerDocument.body || |
573 |
el === el.ownerDocument.documentElement, |
574 |
scrollLeft = isRoot ? $( window ).scrollLeft() : el.scrollLeft, |
575 |
// All browsers use the correct scroll type ('negative') on the root, so don't |
576 |
// do any fixups when looking at the root element |
Error |
Row 577, Column 33: "Prefer getComputedStyle to $.css"
jquery/no-css
|
577 |
direction = isRoot ? 'ltr' : $( el ).css( 'direction' ); |
578 |
|
579 |
if ( direction === 'rtl' ) { |
580 |
if ( rtlScrollType === null ) { |
581 |
test(); |
582 |
} |
583 |
if ( rtlScrollType === 'reverse' ) { |
584 |
scrollLeft = -scrollLeft; |
585 |
} else if ( rtlScrollType === 'default' ) { |
586 |
scrollLeft = scrollLeft - el.scrollWidth + el.clientWidth; |
587 |
} |
588 |
} |
589 |
|
590 |
return scrollLeft; |
591 |
}; |
592 |
}() ); |
593 |
|
594 |
/** |
595 |
* Get the root scrollable element of given element's document. |
596 |
* |
597 |
* On Blink-based browsers (Chrome etc.), `document.documentElement` can't be used to get or set |
598 |
* the scrollTop property; instead we have to use `document.body`. Changing and testing the value |
599 |
* lets us use 'body' or 'documentElement' based on what is working. |
600 |
* |
601 |
* https://code.google.com/p/chromium/issues/detail?id=303131 |
602 |
* |
603 |
* @static |
604 |
* @param {HTMLElement} el Element to find root scrollable parent for |
605 |
* @return {HTMLElement} Scrollable parent, `document.body` or `document.documentElement` |
606 |
* depending on browser |
607 |
*/ |
608 |
OO.ui.Element.static.getRootScrollableElement = function ( el ) { |
609 |
var scrollTop, body; |
610 |
|
611 |
if ( OO.ui.scrollableElement === undefined ) { |
612 |
body = el.ownerDocument.body; |
613 |
scrollTop = body.scrollTop; |
614 |
body.scrollTop = 1; |
615 |
|
616 |
// In some browsers (observed in Chrome 56 on Linux Mint 18.1), |
617 |
// body.scrollTop doesn't become exactly 1, but a fractional value like 0.76 |
618 |
if ( Math.round( body.scrollTop ) === 1 ) { |
619 |
body.scrollTop = scrollTop; |
620 |
OO.ui.scrollableElement = 'body'; |
621 |
} else { |
622 |
OO.ui.scrollableElement = 'documentElement'; |
623 |
} |
624 |
} |
625 |
|
626 |
return el.ownerDocument[ OO.ui.scrollableElement ]; |
627 |
}; |
628 |
|
629 |
/** |
630 |
* Get closest scrollable container. |
631 |
* |
632 |
* Traverses up until either a scrollable element or the root is reached, in which case the root |
633 |
* scrollable element will be returned (see #getRootScrollableElement). |
634 |
* |
635 |
* @static |
636 |
* @param {HTMLElement} el Element to find scrollable container for |
637 |
* @param {string} [dimension] Dimension of scrolling to look for; `x`, `y` or omit for either |
638 |
* @return {HTMLElement} Closest scrollable container |
639 |
*/ |
640 |
OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension ) { |
641 |
var i, val, |
642 |
// Browsers do not correctly return the computed value of 'overflow' when 'overflow-x' and |
643 |
// 'overflow-y' have different values, so we need to check the separate properties. |
644 |
props = [ 'overflow-x', 'overflow-y' ], |
Error |
Row 645, Column 13: "Prefer parentElement to $.parent"
jquery/no-parent
|
645 |
$parent = $( el ).parent(); |
646 |
|
647 |
if ( dimension === 'x' || dimension === 'y' ) { |
648 |
props = [ 'overflow-' + dimension ]; |
649 |
} |
650 |
|
651 |
// Special case for the document root (which doesn't really have any scrollable container, since |
652 |
// it is the ultimate scrollable container, but this is probably saner than null or exception) |
Error |
Row 653, Column 7: "Prefer matches to $.is"
jquery/no-is
|
653 |
if ( $( el ).is( 'html, body' ) ) { |
654 |
return this.getRootScrollableElement( el ); |
655 |
} |
656 |
|
657 |
while ( $parent.length ) { |
658 |
if ( $parent[ 0 ] === this.getRootScrollableElement( el ) ) { |
659 |
return $parent[ 0 ]; |
660 |
} |
661 |
i = props.length; |
662 |
while ( i-- ) { |
Error |
Row 663, Column 10: "Prefer getComputedStyle to $.css"
jquery/no-css
|
663 |
val = $parent.css( props[ i ] ); |
664 |
// We assume that elements with 'overflow' (in any direction) set to 'hidden' will never be |
665 |
// scrolled in that direction, but they can actually be scrolled programatically. The user can |
666 |
// unintentionally perform a scroll in such case even if the application doesn't scroll |
667 |
// programatically, e.g. when jumping to an anchor, or when using built-in find functionality. |
668 |
// This could cause funny issues... |
669 |
if ( val === 'auto' || val === 'scroll' ) { |
670 |
return $parent[ 0 ]; |
671 |
} |
672 |
} |
Error |
Row 673, Column 13: "Prefer parentElement to $.parent"
jquery/no-parent
|
673 |
$parent = $parent.parent(); |
674 |
} |
675 |
// The element is unattached... return something mostly sane |
676 |
return this.getRootScrollableElement( el ); |
677 |
}; |
678 |
|
679 |
/** |
680 |
* Scroll element into view. |
681 |
* |
682 |
* @static |
683 |
* @param {HTMLElement} el Element to scroll into view |
684 |
* @param {Object} [config] Configuration options |
685 |
* @param {string} [config.duration='fast'] jQuery animation duration value |
686 |
* @param {string} [config.direction] Scroll in only one direction, e.g. 'x' or 'y', omit |
687 |
* to scroll in both directions |
688 |
* @return {jQuery.Promise} Promise which resolves when the scroll is complete |
689 |
*/ |
690 |
OO.ui.Element.static.scrollIntoView = function ( el, config ) { |
691 |
var position, animations, container, $container, elementDimensions, containerDimensions, $window, |
Error |
Row 692, Column 14: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
692 |
deferred = $.Deferred(); |
693 |
|
694 |
// Configuration initialization |
695 |
config = config || {}; |
696 |
|
697 |
animations = {}; |
698 |
container = this.getClosestScrollableContainer( el, config.direction ); |
699 |
$container = $( container ); |
700 |
elementDimensions = this.getDimensions( el ); |
701 |
containerDimensions = this.getDimensions( container ); |
702 |
$window = $( this.getWindow( el ) ); |
703 |
|
704 |
// Compute the element's position relative to the container |
Error |
Row 705, Column 7: "Prefer matches to $.is"
jquery/no-is
|
705 |
if ( $container.is( 'html, body' ) ) { |
706 |
// If the scrollable container is the root, this is easy |
707 |
position = { |
708 |
top: elementDimensions.rect.top, |
709 |
bottom: $window.innerHeight() - elementDimensions.rect.bottom, |
710 |
left: elementDimensions.rect.left, |
711 |
right: $window.innerWidth() - elementDimensions.rect.right |
712 |
}; |
713 |
} else { |
714 |
// Otherwise, we have to subtract el's coordinates from container's coordinates |
715 |
position = { |
716 |
top: elementDimensions.rect.top - ( containerDimensions.rect.top + containerDimensions.borders.top ), |
717 |
bottom: containerDimensions.rect.bottom - containerDimensions.borders.bottom - containerDimensions.scrollbar.bottom - elementDimensions.rect.bottom, |
718 |
left: elementDimensions.rect.left - ( containerDimensions.rect.left + containerDimensions.borders.left ), |
719 |
right: containerDimensions.rect.right - containerDimensions.borders.right - containerDimensions.scrollbar.right - elementDimensions.rect.right |
720 |
}; |
721 |
} |
722 |
|
723 |
if ( !config.direction || config.direction === 'y' ) { |
724 |
if ( position.top < 0 ) { |
725 |
animations.scrollTop = containerDimensions.scroll.top + position.top; |
726 |
} else if ( position.top > 0 && position.bottom < 0 ) { |
727 |
animations.scrollTop = containerDimensions.scroll.top + Math.min( position.top, -position.bottom ); |
728 |
} |
729 |
} |
730 |
if ( !config.direction || config.direction === 'x' ) { |
731 |
if ( position.left < 0 ) { |
732 |
animations.scrollLeft = containerDimensions.scroll.left + position.left; |
733 |
} else if ( position.left > 0 && position.right < 0 ) { |
734 |
animations.scrollLeft = containerDimensions.scroll.left + Math.min( position.left, -position.right ); |
735 |
} |
736 |
} |
737 |
if ( !$.isEmptyObject( animations ) ) { |
Error |
Row 738, Column 3: "$.animate is not allowed"
jquery/no-animate
|
738 |
$container.stop( true ).animate( animations, config.duration === undefined ? 'fast' : config.duration ); |
739 |
$container.queue( function ( next ) { |
740 |
deferred.resolve(); |
741 |
next(); |
742 |
} ); |
743 |
} else { |
744 |
deferred.resolve(); |
745 |
} |
746 |
return deferred.promise(); |
747 |
}; |
748 |
|
749 |
/** |
750 |
* Force the browser to reconsider whether it really needs to render scrollbars inside the element |
751 |
* and reserve space for them, because it probably doesn't. |
752 |
* |
753 |
* Workaround primarily for <https://code.google.com/p/chromium/issues/detail?id=387290>, but also |
754 |
* similar bugs in other browsers. "Just" forcing a reflow is not sufficient in all cases, we need |
755 |
* to first actually detach (or hide, but detaching is simpler) all children, *then* force a reflow, |
756 |
* and then reattach (or show) them back. |
757 |
* |
758 |
* @static |
759 |
* @param {HTMLElement} el Element to reconsider the scrollbars on |
760 |
*/ |
761 |
OO.ui.Element.static.reconsiderScrollbars = function ( el ) { |
762 |
var i, len, scrollLeft, scrollTop, nodes = []; |
763 |
// Save scroll position |
764 |
scrollLeft = el.scrollLeft; |
765 |
scrollTop = el.scrollTop; |
766 |
// Detach all children |
767 |
while ( el.firstChild ) { |
768 |
nodes.push( el.firstChild ); |
769 |
el.removeChild( el.firstChild ); |
770 |
} |
771 |
// Force reflow |
772 |
void el.offsetHeight; |
773 |
// Reattach all children |
774 |
for ( i = 0, len = nodes.length; i < len; i++ ) { |
775 |
el.appendChild( nodes[ i ] ); |
776 |
} |
777 |
// Restore scroll position (no-op if scrollbars disappeared) |
778 |
el.scrollLeft = scrollLeft; |
779 |
el.scrollTop = scrollTop; |
780 |
}; |
781 |
|
782 |
/* Methods */ |
783 |
|
784 |
/** |
785 |
* Toggle visibility of an element. |
786 |
* |
787 |
* @param {boolean} [show] Make element visible, omit to toggle visibility |
788 |
* @fires visible |
789 |
* @chainable |
790 |
*/ |
791 |
OO.ui.Element.prototype.toggle = function ( show ) { |
792 |
show = show === undefined ? !this.visible : !!show; |
793 |
|
794 |
if ( show !== this.isVisible() ) { |
795 |
this.visible = show; |
796 |
this.$element.toggleClass( 'oo-ui-element-hidden', !this.visible ); |
797 |
this.emit( 'toggle', show ); |
798 |
} |
799 |
|
800 |
return this; |
801 |
}; |
802 |
|
803 |
/** |
804 |
* Check if element is visible. |
805 |
* |
806 |
* @return {boolean} element is visible |
807 |
*/ |
808 |
OO.ui.Element.prototype.isVisible = function () { |
809 |
return this.visible; |
810 |
}; |
811 |
|
812 |
/** |
813 |
* Get element data. |
814 |
* |
815 |
* @return {Mixed} Element data |
816 |
*/ |
817 |
OO.ui.Element.prototype.getData = function () { |
818 |
return this.data; |
819 |
}; |
820 |
|
821 |
/** |
822 |
* Set element data. |
823 |
* |
824 |
* @param {Mixed} data Element data |
825 |
* @chainable |
826 |
*/ |
827 |
OO.ui.Element.prototype.setData = function ( data ) { |
828 |
this.data = data; |
829 |
return this; |
830 |
}; |
831 |
|
832 |
/** |
833 |
* Set the element has an 'id' attribute. |
834 |
* |
835 |
* @param {string} id |
836 |
* @chainable |
837 |
*/ |
838 |
OO.ui.Element.prototype.setElementId = function ( id ) { |
839 |
this.elementId = id; |
840 |
this.$element.attr( 'id', id ); |
841 |
return this; |
842 |
}; |
843 |
|
844 |
/** |
845 |
* Ensure that the element has an 'id' attribute, setting it to an unique value if it's missing, |
846 |
* and return its value. |
847 |
* |
848 |
* @return {string} |
849 |
*/ |
850 |
OO.ui.Element.prototype.getElementId = function () { |
851 |
if ( this.elementId === null ) { |
852 |
this.setElementId( OO.ui.generateElementId() ); |
853 |
} |
854 |
return this.elementId; |
855 |
}; |
856 |
|
857 |
/** |
858 |
* Check if element supports one or more methods. |
859 |
* |
860 |
* @param {string|string[]} methods Method or list of methods to check |
861 |
* @return {boolean} All methods are supported |
862 |
*/ |
863 |
OO.ui.Element.prototype.supports = function ( methods ) { |
864 |
var i, len, |
865 |
support = 0; |
866 |
|
867 |
methods = Array.isArray( methods ) ? methods : [ methods ]; |
868 |
for ( i = 0, len = methods.length; i < len; i++ ) { |
869 |
if ( $.isFunction( this[ methods[ i ] ] ) ) { |
870 |
support++; |
871 |
} |
872 |
} |
873 |
|
874 |
return methods.length === support; |
875 |
}; |
876 |
|
877 |
/** |
878 |
* Update the theme-provided classes. |
879 |
* |
880 |
* @localdoc This is called in element mixins and widget classes any time state changes. |
881 |
* Updating is debounced, minimizing overhead of changing multiple attributes and |
882 |
* guaranteeing that theme updates do not occur within an element's constructor |
883 |
*/ |
884 |
OO.ui.Element.prototype.updateThemeClasses = function () { |
885 |
OO.ui.theme.queueUpdateElementClasses( this ); |
886 |
}; |
887 |
|
888 |
/** |
889 |
* Get the HTML tag name. |
890 |
* |
891 |
* Override this method to base the result on instance information. |
892 |
* |
893 |
* @return {string} HTML tag name |
894 |
*/ |
895 |
OO.ui.Element.prototype.getTagName = function () { |
896 |
return this.constructor.static.tagName; |
897 |
}; |
898 |
|
899 |
/** |
900 |
* Check if the element is attached to the DOM |
901 |
* |
902 |
* @return {boolean} The element is attached to the DOM |
903 |
*/ |
904 |
OO.ui.Element.prototype.isElementAttached = function () { |
905 |
return $.contains( this.getElementDocument(), this.$element[ 0 ] ); |
906 |
}; |
907 |
|
908 |
/** |
909 |
* Get the DOM document. |
910 |
* |
911 |
* @return {HTMLDocument} Document object |
912 |
*/ |
913 |
OO.ui.Element.prototype.getElementDocument = function () { |
914 |
// Don't cache this in other ways either because subclasses could can change this.$element |
915 |
return OO.ui.Element.static.getDocument( this.$element ); |
916 |
}; |
917 |
|
918 |
/** |
919 |
* Get the DOM window. |
920 |
* |
921 |
* @return {Window} Window object |
922 |
*/ |
923 |
OO.ui.Element.prototype.getElementWindow = function () { |
924 |
return OO.ui.Element.static.getWindow( this.$element ); |
925 |
}; |
926 |
|
927 |
/** |
928 |
* Get closest scrollable container. |
929 |
* |
930 |
* @return {HTMLElement} Closest scrollable container |
931 |
*/ |
932 |
OO.ui.Element.prototype.getClosestScrollableElementContainer = function () { |
933 |
return OO.ui.Element.static.getClosestScrollableContainer( this.$element[ 0 ] ); |
934 |
}; |
935 |
|
936 |
/** |
937 |
* Get group element is in. |
938 |
* |
939 |
* @return {OO.ui.mixin.GroupElement|null} Group element, null if none |
940 |
*/ |
941 |
OO.ui.Element.prototype.getElementGroup = function () { |
942 |
return this.elementGroup; |
943 |
}; |
944 |
|
945 |
/** |
946 |
* Set group element is in. |
947 |
* |
948 |
* @param {OO.ui.mixin.GroupElement|null} group Group element, null if none |
949 |
* @chainable |
950 |
*/ |
951 |
OO.ui.Element.prototype.setElementGroup = function ( group ) { |
952 |
this.elementGroup = group; |
953 |
return this; |
954 |
}; |
955 |
|
956 |
/** |
957 |
* Scroll element into view. |
958 |
* |
959 |
* @param {Object} [config] Configuration options |
960 |
* @return {jQuery.Promise} Promise which resolves when the scroll is complete |
961 |
*/ |
962 |
OO.ui.Element.prototype.scrollElementIntoView = function ( config ) { |
963 |
if ( |
964 |
!this.isElementAttached() || |
965 |
!this.isVisible() || |
966 |
( this.getElementGroup() && !this.getElementGroup().isVisible() ) |
967 |
) { |
Error |
Row 968, Column 10: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
968 |
return $.Deferred().resolve(); |
969 |
} |
970 |
return OO.ui.Element.static.scrollIntoView( this.$element[ 0 ], config ); |
971 |
}; |
972 |
|
973 |
/** |
974 |
* Restore the pre-infusion dynamic state for this widget. |
975 |
* |
976 |
* This method is called after #$element has been inserted into DOM. The parameter is the return |
977 |
* value of #gatherPreInfuseState. |
978 |
* |
979 |
* @protected |
980 |
* @param {Object} state |
981 |
*/ |
982 |
OO.ui.Element.prototype.restorePreInfuseState = function () { |
983 |
}; |
984 |
|
|
|
/src/Error.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 81, Column 3: "Prefer textContent to $.text"
jquery/no-text
|
Line |
Source |
1 |
/** |
2 |
* Errors contain a required message (either a string or jQuery selection) that is used to describe what went wrong |
3 |
* in a {@link OO.ui.Process process}. The error's #recoverable and #warning configurations are used to customize the |
4 |
* appearance and functionality of the error interface. |
5 |
* |
6 |
* The basic error interface contains a formatted error message as well as two buttons: 'Dismiss' and 'Try again' (i.e., the error |
7 |
* is 'recoverable' by default). If the error is not recoverable, the 'Try again' button will not be rendered and the widget |
8 |
* that initiated the failed process will be disabled. |
9 |
* |
10 |
* If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button, which will try the |
11 |
* process again. |
12 |
* |
13 |
* For an example of error interfaces, please see the [OOUI documentation on MediaWiki][1]. |
14 |
* |
15 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs#Processes_and_errors |
16 |
* |
17 |
* @class |
18 |
* |
19 |
* @constructor |
20 |
* @param {string|jQuery} message Description of error |
21 |
* @param {Object} [config] Configuration options |
22 |
* @cfg {boolean} [recoverable=true] Error is recoverable. |
23 |
* By default, errors are recoverable, and users can try the process again. |
24 |
* @cfg {boolean} [warning=false] Error is a warning. |
25 |
* If the error is a warning, the error interface will include a |
26 |
* 'Dismiss' and a 'Continue' button. It is the responsibility of the developer to ensure that the warning |
27 |
* is not triggered a second time if the user chooses to continue. |
28 |
*/ |
29 |
OO.ui.Error = function OoUiError( message, config ) { |
30 |
// Allow passing positional parameters inside the config object |
31 |
if ( OO.isPlainObject( message ) && config === undefined ) { |
32 |
config = message; |
33 |
message = config.message; |
34 |
} |
35 |
|
36 |
// Configuration initialization |
37 |
config = config || {}; |
38 |
|
39 |
// Properties |
40 |
this.message = message instanceof jQuery ? message : String( message ); |
41 |
this.recoverable = config.recoverable === undefined || !!config.recoverable; |
42 |
this.warning = !!config.warning; |
43 |
}; |
44 |
|
45 |
/* Setup */ |
46 |
|
47 |
OO.initClass( OO.ui.Error ); |
48 |
|
49 |
/* Methods */ |
50 |
|
51 |
/** |
52 |
* Check if the error is recoverable. |
53 |
* |
54 |
* If the error is recoverable, users are able to try the process again. |
55 |
* |
56 |
* @return {boolean} Error is recoverable |
57 |
*/ |
58 |
OO.ui.Error.prototype.isRecoverable = function () { |
59 |
return this.recoverable; |
60 |
}; |
61 |
|
62 |
/** |
63 |
* Check if the error is a warning. |
64 |
* |
65 |
* If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button. |
66 |
* |
67 |
* @return {boolean} Error is warning |
68 |
*/ |
69 |
OO.ui.Error.prototype.isWarning = function () { |
70 |
return this.warning; |
71 |
}; |
72 |
|
73 |
/** |
74 |
* Get error message as DOM nodes. |
75 |
* |
76 |
* @return {jQuery} Error message in DOM nodes |
77 |
*/ |
78 |
OO.ui.Error.prototype.getMessage = function () { |
79 |
return this.message instanceof jQuery ? |
80 |
this.message.clone() : |
Error |
Row 81, Column 3: "Prefer textContent to $.text"
jquery/no-text
|
81 |
$( '<div>' ).text( this.message ).contents(); |
82 |
}; |
83 |
|
84 |
/** |
85 |
* Get the error message text. |
86 |
* |
87 |
* @return {string} Error message |
88 |
*/ |
89 |
OO.ui.Error.prototype.getMessageText = function () { |
90 |
return this.message instanceof jQuery ? this.message.text() : this.message; |
91 |
}; |
92 |
|
|
|
/src/HtmlSnippet.js
|
0 problems
|
|
/src/Layout.js
|
0 problems
|
|
/src/layouts/ActionFieldLayout.js
|
0 problems
|
|
/src/layouts/BookletLayout.js
|
2 problems (2 errors, 0 warnings)
|
Line |
Source |
1 |
/** |
2 |
* BookletLayouts contain {@link OO.ui.PageLayout page layouts} as well as |
3 |
* an {@link OO.ui.OutlineSelectWidget outline} that allows users to easily navigate |
4 |
* through the pages and select which one to display. By default, only one page is |
5 |
* displayed at a time and the outline is hidden. When a user navigates to a new page, |
6 |
* the booklet layout automatically focuses on the first focusable element, unless the |
7 |
* default setting is changed. Optionally, booklets can be configured to show |
8 |
* {@link OO.ui.OutlineControlsWidget controls} for adding, moving, and removing items. |
9 |
* |
10 |
* @example |
11 |
* // Example of a BookletLayout that contains two PageLayouts. |
12 |
* |
13 |
* function PageOneLayout( name, config ) { |
14 |
* PageOneLayout.parent.call( this, name, config ); |
15 |
* this.$element.append( '<p>First page</p><p>(This booklet has an outline, displayed on the left)</p>' ); |
16 |
* } |
17 |
* OO.inheritClass( PageOneLayout, OO.ui.PageLayout ); |
18 |
* PageOneLayout.prototype.setupOutlineItem = function () { |
19 |
* this.outlineItem.setLabel( 'Page One' ); |
20 |
* }; |
21 |
* |
22 |
* function PageTwoLayout( name, config ) { |
23 |
* PageTwoLayout.parent.call( this, name, config ); |
24 |
* this.$element.append( '<p>Second page</p>' ); |
25 |
* } |
26 |
* OO.inheritClass( PageTwoLayout, OO.ui.PageLayout ); |
27 |
* PageTwoLayout.prototype.setupOutlineItem = function () { |
28 |
* this.outlineItem.setLabel( 'Page Two' ); |
29 |
* }; |
30 |
* |
31 |
* var page1 = new PageOneLayout( 'one' ), |
32 |
* page2 = new PageTwoLayout( 'two' ); |
33 |
* |
34 |
* var booklet = new OO.ui.BookletLayout( { |
35 |
* outlined: true |
36 |
* } ); |
37 |
* |
38 |
* booklet.addPages( [ page1, page2 ] ); |
39 |
* $( 'body' ).append( booklet.$element ); |
40 |
* |
41 |
* @class |
42 |
* @extends OO.ui.MenuLayout |
43 |
* |
44 |
* @constructor |
45 |
* @param {Object} [config] Configuration options |
46 |
* @cfg {boolean} [continuous=false] Show all pages, one after another |
47 |
* @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new page is displayed. Disabled on mobile. |
48 |
* @cfg {boolean} [outlined=false] Show the outline. The outline is used to navigate through the pages of the booklet. |
49 |
* @cfg {boolean} [editable=false] Show controls for adding, removing and reordering pages |
50 |
*/ |
51 |
OO.ui.BookletLayout = function OoUiBookletLayout( config ) { |
52 |
// Configuration initialization |
53 |
config = config || {}; |
54 |
|
55 |
// Parent constructor |
56 |
OO.ui.BookletLayout.parent.call( this, config ); |
57 |
|
58 |
// Properties |
59 |
this.currentPageName = null; |
60 |
this.pages = {}; |
61 |
this.ignoreFocus = false; |
62 |
this.stackLayout = new OO.ui.StackLayout( { |
63 |
continuous: !!config.continuous, |
64 |
expanded: this.expanded |
65 |
} ); |
66 |
this.setContentPanel( this.stackLayout ); |
67 |
this.autoFocus = config.autoFocus === undefined || !!config.autoFocus; |
68 |
this.outlineVisible = false; |
69 |
this.outlined = !!config.outlined; |
70 |
if ( this.outlined ) { |
71 |
this.editable = !!config.editable; |
72 |
this.outlineControlsWidget = null; |
73 |
this.outlineSelectWidget = new OO.ui.OutlineSelectWidget(); |
74 |
this.outlinePanel = new OO.ui.PanelLayout( { |
75 |
expanded: this.expanded, |
76 |
scrollable: true |
77 |
} ); |
78 |
this.setMenuPanel( this.outlinePanel ); |
79 |
this.outlineVisible = true; |
80 |
if ( this.editable ) { |
81 |
this.outlineControlsWidget = new OO.ui.OutlineControlsWidget( |
82 |
this.outlineSelectWidget |
83 |
); |
84 |
} |
85 |
} |
86 |
this.toggleMenu( this.outlined ); |
87 |
|
88 |
// Events |
89 |
this.stackLayout.connect( this, { set: 'onStackLayoutSet' } ); |
90 |
if ( this.outlined ) { |
91 |
this.outlineSelectWidget.connect( this, { select: 'onOutlineSelectWidgetSelect' } ); |
92 |
this.scrolling = false; |
93 |
this.stackLayout.connect( this, { visibleItemChange: 'onStackLayoutVisibleItemChange' } ); |
94 |
} |
95 |
if ( this.autoFocus ) { |
96 |
// Event 'focus' does not bubble, but 'focusin' does |
97 |
this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) ); |
98 |
} |
99 |
|
100 |
// Initialization |
101 |
this.$element.addClass( 'oo-ui-bookletLayout' ); |
102 |
this.stackLayout.$element.addClass( 'oo-ui-bookletLayout-stackLayout' ); |
103 |
if ( this.outlined ) { |
104 |
this.outlinePanel.$element |
105 |
.addClass( 'oo-ui-bookletLayout-outlinePanel' ) |
106 |
.append( this.outlineSelectWidget.$element ); |
107 |
if ( this.editable ) { |
108 |
this.outlinePanel.$element |
109 |
.addClass( 'oo-ui-bookletLayout-outlinePanel-editable' ) |
110 |
.append( this.outlineControlsWidget.$element ); |
111 |
} |
112 |
} |
113 |
}; |
114 |
|
115 |
/* Setup */ |
116 |
|
117 |
OO.inheritClass( OO.ui.BookletLayout, OO.ui.MenuLayout ); |
118 |
|
119 |
/* Events */ |
120 |
|
121 |
/** |
122 |
* A 'set' event is emitted when a page is {@link #setPage set} to be displayed by the booklet layout. |
123 |
* @event set |
124 |
* @param {OO.ui.PageLayout} page Current page |
125 |
*/ |
126 |
|
127 |
/** |
128 |
* An 'add' event is emitted when pages are {@link #addPages added} to the booklet layout. |
129 |
* |
130 |
* @event add |
131 |
* @param {OO.ui.PageLayout[]} page Added pages |
132 |
* @param {number} index Index pages were added at |
133 |
*/ |
134 |
|
135 |
/** |
136 |
* A 'remove' event is emitted when pages are {@link #clearPages cleared} or |
137 |
* {@link #removePages removed} from the booklet. |
138 |
* |
139 |
* @event remove |
140 |
* @param {OO.ui.PageLayout[]} pages Removed pages |
141 |
*/ |
142 |
|
143 |
/* Methods */ |
144 |
|
145 |
/** |
146 |
* Handle stack layout focus. |
147 |
* |
148 |
* @private |
149 |
* @param {jQuery.Event} e Focusin event |
150 |
*/ |
151 |
OO.ui.BookletLayout.prototype.onStackLayoutFocus = function ( e ) { |
152 |
var name, $target; |
153 |
|
154 |
// Find the page that an element was focused within |
Error |
Row 155, Column 12: "Prefer closest to $.closest"
jquery/no-closest
|
155 |
$target = $( e.target ).closest( '.oo-ui-pageLayout' ); |
156 |
for ( name in this.pages ) { |
157 |
// Check for page match, exclude current page to find only page changes |
158 |
if ( this.pages[ name ].$element[ 0 ] === $target[ 0 ] && name !== this.currentPageName ) { |
159 |
this.setPage( name ); |
160 |
break; |
161 |
} |
162 |
} |
163 |
}; |
164 |
|
165 |
/** |
166 |
* Handle visibleItemChange events from the stackLayout |
167 |
* |
168 |
* The next visible page is set as the current page by selecting it |
169 |
* in the outline |
170 |
* |
171 |
* @param {OO.ui.PageLayout} page The next visible page in the layout |
172 |
*/ |
173 |
OO.ui.BookletLayout.prototype.onStackLayoutVisibleItemChange = function ( page ) { |
174 |
// Set a flag to so that the resulting call to #onStackLayoutSet doesn't |
175 |
// try and scroll the item into view again. |
176 |
this.scrolling = true; |
177 |
this.outlineSelectWidget.selectItemByData( page.getName() ); |
178 |
this.scrolling = false; |
179 |
}; |
180 |
|
181 |
/** |
182 |
* Handle stack layout set events. |
183 |
* |
184 |
* @private |
185 |
* @param {OO.ui.PanelLayout|null} page The page panel that is now the current panel |
186 |
*/ |
187 |
OO.ui.BookletLayout.prototype.onStackLayoutSet = function ( page ) { |
188 |
var promise, layout = this; |
189 |
// If everything is unselected, do nothing |
190 |
if ( !page ) { |
191 |
return; |
192 |
} |
193 |
// For continuous BookletLayouts, scroll the selected page into view first |
194 |
if ( this.stackLayout.continuous && !this.scrolling ) { |
195 |
promise = page.scrollElementIntoView(); |
196 |
} else { |
Error |
Row 197, Column 13: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
197 |
promise = $.Deferred().resolve(); |
198 |
} |
199 |
// Focus the first element on the newly selected panel. |
200 |
// Don't focus if the page was set by scrolling. |
201 |
if ( this.autoFocus && !OO.ui.isMobile() && !this.scrolling ) { |
202 |
promise.done( function () { |
203 |
layout.focus(); |
204 |
} ); |
205 |
} |
206 |
}; |
207 |
|
208 |
/** |
209 |
* Focus the first input in the current page. |
210 |
* |
211 |
* If no page is selected, the first selectable page will be selected. |
212 |
* If the focus is already in an element on the current page, nothing will happen. |
213 |
* |
214 |
* @param {number} [itemIndex] A specific item to focus on |
215 |
*/ |
216 |
OO.ui.BookletLayout.prototype.focus = function ( itemIndex ) { |
217 |
var page, |
218 |
items = this.stackLayout.getItems(); |
219 |
|
220 |
if ( itemIndex !== undefined && items[ itemIndex ] ) { |
221 |
page = items[ itemIndex ]; |
222 |
} else { |
223 |
page = this.stackLayout.getCurrentItem(); |
224 |
} |
225 |
|
226 |
if ( !page && this.outlined ) { |
227 |
this.selectFirstSelectablePage(); |
228 |
page = this.stackLayout.getCurrentItem(); |
229 |
} |
230 |
if ( !page ) { |
231 |
return; |
232 |
} |
233 |
// Only change the focus if is not already in the current page |
234 |
if ( !OO.ui.contains( page.$element[ 0 ], this.getElementDocument().activeElement, true ) ) { |
235 |
page.focus(); |
236 |
} |
237 |
}; |
238 |
|
239 |
/** |
240 |
* Find the first focusable input in the booklet layout and focus |
241 |
* on it. |
242 |
*/ |
243 |
OO.ui.BookletLayout.prototype.focusFirstFocusable = function () { |
244 |
OO.ui.findFocusable( this.stackLayout.$element ).focus(); |
245 |
}; |
246 |
|
247 |
/** |
248 |
* Handle outline widget select events. |
249 |
* |
250 |
* @private |
251 |
* @param {OO.ui.OptionWidget|null} item Selected item |
252 |
*/ |
253 |
OO.ui.BookletLayout.prototype.onOutlineSelectWidgetSelect = function ( item ) { |
254 |
if ( item ) { |
255 |
this.setPage( item.getData() ); |
256 |
} |
257 |
}; |
258 |
|
259 |
/** |
260 |
* Check if booklet has an outline. |
261 |
* |
262 |
* @return {boolean} Booklet has an outline |
263 |
*/ |
264 |
OO.ui.BookletLayout.prototype.isOutlined = function () { |
265 |
return this.outlined; |
266 |
}; |
267 |
|
268 |
/** |
269 |
* Check if booklet has editing controls. |
270 |
* |
271 |
* @return {boolean} Booklet is editable |
272 |
*/ |
273 |
OO.ui.BookletLayout.prototype.isEditable = function () { |
274 |
return this.editable; |
275 |
}; |
276 |
|
277 |
/** |
278 |
* Check if booklet has a visible outline. |
279 |
* |
280 |
* @return {boolean} Outline is visible |
281 |
*/ |
282 |
OO.ui.BookletLayout.prototype.isOutlineVisible = function () { |
283 |
return this.outlined && this.outlineVisible; |
284 |
}; |
285 |
|
286 |
/** |
287 |
* Hide or show the outline. |
288 |
* |
289 |
* @param {boolean} [show] Show outline, omit to invert current state |
290 |
* @chainable |
291 |
*/ |
292 |
OO.ui.BookletLayout.prototype.toggleOutline = function ( show ) { |
293 |
var booklet = this; |
294 |
|
295 |
if ( this.outlined ) { |
296 |
show = show === undefined ? !this.outlineVisible : !!show; |
297 |
this.outlineVisible = show; |
298 |
this.toggleMenu( show ); |
299 |
if ( show && this.editable ) { |
300 |
// HACK: Kill dumb scrollbars when the sidebar stops animating, see T161798. Only necessary when |
301 |
// outline controls are present, delay matches transition on `.oo-ui-menuLayout-menu`. |
302 |
setTimeout( function () { |
303 |
OO.ui.Element.static.reconsiderScrollbars( booklet.outlinePanel.$element[ 0 ] ); |
304 |
}, 200 ); |
305 |
} |
306 |
} |
307 |
|
308 |
return this; |
309 |
}; |
310 |
|
311 |
/** |
312 |
* Find the page closest to the specified page. |
313 |
* |
314 |
* @param {OO.ui.PageLayout} page Page to use as a reference point |
315 |
* @return {OO.ui.PageLayout|null} Page closest to the specified page |
316 |
*/ |
317 |
OO.ui.BookletLayout.prototype.findClosestPage = function ( page ) { |
318 |
var next, prev, level, |
319 |
pages = this.stackLayout.getItems(), |
320 |
index = pages.indexOf( page ); |
321 |
|
322 |
if ( index !== -1 ) { |
323 |
next = pages[ index + 1 ]; |
324 |
prev = pages[ index - 1 ]; |
325 |
// Prefer adjacent pages at the same level |
326 |
if ( this.outlined ) { |
327 |
level = this.outlineSelectWidget.findItemFromData( page.getName() ).getLevel(); |
328 |
if ( |
329 |
prev && |
330 |
level === this.outlineSelectWidget.findItemFromData( prev.getName() ).getLevel() |
331 |
) { |
332 |
return prev; |
333 |
} |
334 |
if ( |
335 |
next && |
336 |
level === this.outlineSelectWidget.findItemFromData( next.getName() ).getLevel() |
337 |
) { |
338 |
return next; |
339 |
} |
340 |
} |
341 |
} |
342 |
return prev || next || null; |
343 |
}; |
344 |
|
345 |
/** |
346 |
* Get the outline widget. |
347 |
* |
348 |
* If the booklet is not outlined, the method will return `null`. |
349 |
* |
350 |
* @return {OO.ui.OutlineSelectWidget|null} Outline widget, or null if the booklet is not outlined |
351 |
*/ |
352 |
OO.ui.BookletLayout.prototype.getOutline = function () { |
353 |
return this.outlineSelectWidget; |
354 |
}; |
355 |
|
356 |
/** |
357 |
* Get the outline controls widget. |
358 |
* |
359 |
* If the outline is not editable, the method will return `null`. |
360 |
* |
361 |
* @return {OO.ui.OutlineControlsWidget|null} The outline controls widget. |
362 |
*/ |
363 |
OO.ui.BookletLayout.prototype.getOutlineControls = function () { |
364 |
return this.outlineControlsWidget; |
365 |
}; |
366 |
|
367 |
/** |
368 |
* Get a page by its symbolic name. |
369 |
* |
370 |
* @param {string} name Symbolic name of page |
371 |
* @return {OO.ui.PageLayout|undefined} Page, if found |
372 |
*/ |
373 |
OO.ui.BookletLayout.prototype.getPage = function ( name ) { |
374 |
return this.pages[ name ]; |
375 |
}; |
376 |
|
377 |
/** |
378 |
* Get the current page. |
379 |
* |
380 |
* @return {OO.ui.PageLayout|undefined} Current page, if found |
381 |
*/ |
382 |
OO.ui.BookletLayout.prototype.getCurrentPage = function () { |
383 |
var name = this.getCurrentPageName(); |
384 |
return name ? this.getPage( name ) : undefined; |
385 |
}; |
386 |
|
387 |
/** |
388 |
* Get the symbolic name of the current page. |
389 |
* |
390 |
* @return {string|null} Symbolic name of the current page |
391 |
*/ |
392 |
OO.ui.BookletLayout.prototype.getCurrentPageName = function () { |
393 |
return this.currentPageName; |
394 |
}; |
395 |
|
396 |
/** |
397 |
* Add pages to the booklet layout |
398 |
* |
399 |
* When pages are added with the same names as existing pages, the existing pages will be |
400 |
* automatically removed before the new pages are added. |
401 |
* |
402 |
* @param {OO.ui.PageLayout[]} pages Pages to add |
403 |
* @param {number} index Index of the insertion point |
404 |
* @fires add |
405 |
* @chainable |
406 |
*/ |
407 |
OO.ui.BookletLayout.prototype.addPages = function ( pages, index ) { |
408 |
var i, len, name, page, item, currentIndex, |
409 |
stackLayoutPages = this.stackLayout.getItems(), |
410 |
remove = [], |
411 |
items = []; |
412 |
|
413 |
// Remove pages with same names |
414 |
for ( i = 0, len = pages.length; i < len; i++ ) { |
415 |
page = pages[ i ]; |
416 |
name = page.getName(); |
417 |
|
418 |
if ( Object.prototype.hasOwnProperty.call( this.pages, name ) ) { |
419 |
// Correct the insertion index |
420 |
currentIndex = stackLayoutPages.indexOf( this.pages[ name ] ); |
421 |
if ( currentIndex !== -1 && currentIndex + 1 < index ) { |
422 |
index--; |
423 |
} |
424 |
remove.push( this.pages[ name ] ); |
425 |
} |
426 |
} |
427 |
if ( remove.length ) { |
428 |
this.removePages( remove ); |
429 |
} |
430 |
|
431 |
// Add new pages |
432 |
for ( i = 0, len = pages.length; i < len; i++ ) { |
433 |
page = pages[ i ]; |
434 |
name = page.getName(); |
435 |
this.pages[ page.getName() ] = page; |
436 |
if ( this.outlined ) { |
437 |
item = new OO.ui.OutlineOptionWidget( { data: name } ); |
438 |
page.setOutlineItem( item ); |
439 |
items.push( item ); |
440 |
} |
441 |
} |
442 |
|
443 |
if ( this.outlined && items.length ) { |
444 |
this.outlineSelectWidget.addItems( items, index ); |
445 |
this.selectFirstSelectablePage(); |
446 |
} |
447 |
this.stackLayout.addItems( pages, index ); |
448 |
this.emit( 'add', pages, index ); |
449 |
|
450 |
return this; |
451 |
}; |
452 |
|
453 |
/** |
454 |
* Remove the specified pages from the booklet layout. |
455 |
* |
456 |
* To remove all pages from the booklet, you may wish to use the #clearPages method instead. |
457 |
* |
458 |
* @param {OO.ui.PageLayout[]} pages An array of pages to remove |
459 |
* @fires remove |
460 |
* @chainable |
461 |
*/ |
462 |
OO.ui.BookletLayout.prototype.removePages = function ( pages ) { |
463 |
var i, len, name, page, |
464 |
items = []; |
465 |
|
466 |
for ( i = 0, len = pages.length; i < len; i++ ) { |
467 |
page = pages[ i ]; |
468 |
name = page.getName(); |
469 |
delete this.pages[ name ]; |
470 |
if ( this.outlined ) { |
471 |
items.push( this.outlineSelectWidget.findItemFromData( name ) ); |
472 |
page.setOutlineItem( null ); |
473 |
} |
474 |
} |
475 |
if ( this.outlined && items.length ) { |
476 |
this.outlineSelectWidget.removeItems( items ); |
477 |
this.selectFirstSelectablePage(); |
478 |
} |
479 |
this.stackLayout.removeItems( pages ); |
480 |
this.emit( 'remove', pages ); |
481 |
|
482 |
return this; |
483 |
}; |
484 |
|
485 |
/** |
486 |
* Clear all pages from the booklet layout. |
487 |
* |
488 |
* To remove only a subset of pages from the booklet, use the #removePages method. |
489 |
* |
490 |
* @fires remove |
491 |
* @chainable |
492 |
*/ |
493 |
OO.ui.BookletLayout.prototype.clearPages = function () { |
494 |
var i, len, |
495 |
pages = this.stackLayout.getItems(); |
496 |
|
497 |
this.pages = {}; |
498 |
this.currentPageName = null; |
499 |
if ( this.outlined ) { |
500 |
this.outlineSelectWidget.clearItems(); |
501 |
for ( i = 0, len = pages.length; i < len; i++ ) { |
502 |
pages[ i ].setOutlineItem( null ); |
503 |
} |
504 |
} |
505 |
this.stackLayout.clearItems(); |
506 |
|
507 |
this.emit( 'remove', pages ); |
508 |
|
509 |
return this; |
510 |
}; |
511 |
|
512 |
/** |
513 |
* Set the current page by symbolic name. |
514 |
* |
515 |
* @fires set |
516 |
* @param {string} name Symbolic name of page |
517 |
*/ |
518 |
OO.ui.BookletLayout.prototype.setPage = function ( name ) { |
519 |
var selectedItem, |
520 |
$focused, |
521 |
page = this.pages[ name ], |
522 |
previousPage = this.currentPageName && this.pages[ this.currentPageName ]; |
523 |
|
524 |
if ( name !== this.currentPageName ) { |
525 |
if ( this.outlined ) { |
526 |
selectedItem = this.outlineSelectWidget.findSelectedItem(); |
527 |
if ( selectedItem && selectedItem.getData() !== name ) { |
528 |
this.outlineSelectWidget.selectItemByData( name ); |
529 |
} |
530 |
} |
531 |
if ( page ) { |
532 |
if ( previousPage ) { |
533 |
previousPage.setActive( false ); |
534 |
// Blur anything focused if the next page doesn't have anything focusable. |
535 |
// This is not needed if the next page has something focusable (because once it is focused |
536 |
// this blur happens automatically). If the layout is non-continuous, this check is |
537 |
// meaningless because the next page is not visible yet and thus can't hold focus. |
538 |
if ( |
539 |
this.autoFocus && |
540 |
!OO.ui.isMobile() && |
541 |
this.stackLayout.continuous && |
542 |
OO.ui.findFocusable( page.$element ).length !== 0 |
543 |
) { |
544 |
$focused = previousPage.$element.find( ':focus' ); |
545 |
if ( $focused.length ) { |
546 |
$focused[ 0 ].blur(); |
547 |
} |
548 |
} |
549 |
} |
550 |
this.currentPageName = name; |
551 |
page.setActive( true ); |
552 |
this.stackLayout.setItem( page ); |
553 |
if ( !this.stackLayout.continuous && previousPage ) { |
554 |
// This should not be necessary, since any inputs on the previous page should have been |
555 |
// blurred when it was hidden, but browsers are not very consistent about this. |
556 |
$focused = previousPage.$element.find( ':focus' ); |
557 |
if ( $focused.length ) { |
558 |
$focused[ 0 ].blur(); |
559 |
} |
560 |
} |
561 |
this.emit( 'set', page ); |
562 |
} |
563 |
} |
564 |
}; |
565 |
|
566 |
/** |
567 |
* For outlined-continuous booklets, also reset the outlineSelectWidget to the first item. |
568 |
* |
569 |
* @inheritdoc |
570 |
*/ |
571 |
OO.ui.BookletLayout.prototype.resetScroll = function () { |
572 |
// Parent method |
573 |
OO.ui.BookletLayout.parent.prototype.resetScroll.call( this ); |
574 |
|
575 |
if ( this.outlined && this.stackLayout.continuous && this.outlineSelectWidget.findFirstSelectableItem() ) { |
576 |
this.scrolling = true; |
577 |
this.outlineSelectWidget.selectItem( this.outlineSelectWidget.findFirstSelectableItem() ); |
578 |
this.scrolling = false; |
579 |
} |
580 |
return this; |
581 |
}; |
582 |
|
583 |
/** |
584 |
* Select the first selectable page. |
585 |
* |
586 |
* @chainable |
587 |
*/ |
588 |
OO.ui.BookletLayout.prototype.selectFirstSelectablePage = function () { |
589 |
if ( !this.outlineSelectWidget.findSelectedItem() ) { |
590 |
this.outlineSelectWidget.selectItem( this.outlineSelectWidget.findFirstSelectableItem() ); |
591 |
} |
592 |
|
593 |
return this; |
594 |
}; |
595 |
|
|
|
/src/layouts/FieldLayout.js
|
2 problems (2 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 184, Column 3: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 191, Column 2: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* FieldLayouts are used with OO.ui.FieldsetLayout. Each FieldLayout requires a field-widget, |
3 |
* which is a widget that is specified by reference before any optional configuration settings. |
4 |
* |
5 |
* Field layouts can be configured with help text and/or labels. Labels are aligned in one of four ways: |
6 |
* |
7 |
* - **left**: The label is placed before the field-widget and aligned with the left margin. |
8 |
* A left-alignment is used for forms with many fields. |
9 |
* - **right**: The label is placed before the field-widget and aligned to the right margin. |
10 |
* A right-alignment is used for long but familiar forms which users tab through, |
11 |
* verifying the current field with a quick glance at the label. |
12 |
* - **top**: The label is placed above the field-widget. A top-alignment is used for brief forms |
13 |
* that users fill out from top to bottom. |
14 |
* - **inline**: The label is placed after the field-widget and aligned to the left. |
15 |
* An inline-alignment is best used with checkboxes or radio buttons. |
16 |
* |
17 |
* Help text can either be: |
18 |
* |
19 |
* - accessed via a help icon that appears in the upper right corner of the rendered field layout, or |
20 |
* - shown as a subtle explanation below the label. |
21 |
* |
22 |
* If the help text is brief, or is essential to always expose it, set `helpInline` to `true`. If it |
23 |
* is long or not essential, leave `helpInline` to its default, `false`. |
24 |
* |
25 |
* Please see the [OOUI documentation on MediaWiki] [1] for examples and more information. |
26 |
* |
27 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Layouts/Fields_and_Fieldsets |
28 |
* |
29 |
* @class |
30 |
* @extends OO.ui.Layout |
31 |
* @mixins OO.ui.mixin.LabelElement |
32 |
* @mixins OO.ui.mixin.TitledElement |
33 |
* |
34 |
* @constructor |
35 |
* @param {OO.ui.Widget} fieldWidget Field widget |
36 |
* @param {Object} [config] Configuration options |
37 |
* @cfg {string} [align='left'] Alignment of the label: 'left', 'right', 'top' |
38 |
* or 'inline' |
39 |
* @cfg {Array} [errors] Error messages about the widget, which will be |
40 |
* displayed below the widget. |
41 |
* The array may contain strings or OO.ui.HtmlSnippet instances. |
42 |
* @cfg {Array} [notices] Notices about the widget, which will be displayed |
43 |
* below the widget. |
44 |
* The array may contain strings or OO.ui.HtmlSnippet instances. |
45 |
* These are more visible than `help` messages when `helpInline` is set, and so |
46 |
* might be good for transient messages. |
47 |
* @cfg {string|OO.ui.HtmlSnippet} [help] Help text. When help text is specified |
48 |
* and `helpInline` is `false`, a "help" icon will appear in the upper-right |
49 |
* corner of the rendered field; clicking it will display the text in a popup. |
50 |
* If `helpInline` is `true`, then a subtle description will be shown after the |
51 |
* label. |
52 |
* @cfg {boolean} [helpInline=false] Whether or not the help should be inline, |
53 |
* or shown when the "help" icon is clicked. |
54 |
* @cfg {jQuery} [$overlay] Passed to OO.ui.PopupButtonWidget for help popup, if |
55 |
* `help` is given. |
56 |
* See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>. |
57 |
* |
58 |
* @throws {Error} An error is thrown if no widget is specified |
59 |
*/ |
60 |
OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) { |
61 |
// Allow passing positional parameters inside the config object |
62 |
if ( OO.isPlainObject( fieldWidget ) && config === undefined ) { |
63 |
config = fieldWidget; |
64 |
fieldWidget = config.fieldWidget; |
65 |
} |
66 |
|
67 |
// Make sure we have required constructor arguments |
68 |
if ( fieldWidget === undefined ) { |
69 |
throw new Error( 'Widget not found' ); |
70 |
} |
71 |
|
72 |
// Configuration initialization |
73 |
config = $.extend( { align: 'left', helpInline: false }, config ); |
74 |
|
75 |
// Parent constructor |
76 |
OO.ui.FieldLayout.parent.call( this, config ); |
77 |
|
78 |
// Mixin constructors |
79 |
OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { |
80 |
$label: $( '<label>' ) |
81 |
} ) ); |
82 |
OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$label } ) ); |
83 |
|
84 |
// Properties |
85 |
this.fieldWidget = fieldWidget; |
86 |
this.errors = []; |
87 |
this.notices = []; |
88 |
this.$field = this.isFieldInline() ? $( '<span>' ) : $( '<div>' ); |
89 |
this.$messages = $( '<ul>' ); |
90 |
this.$header = $( '<span>' ); |
91 |
this.$body = $( '<div>' ); |
92 |
this.align = null; |
93 |
this.helpInline = config.helpInline; |
94 |
|
95 |
// Events |
96 |
this.fieldWidget.connect( this, { disable: 'onFieldDisable' } ); |
97 |
|
98 |
// Initialization |
99 |
this.$help = config.help ? |
100 |
this.createHelpElement( config.help, config.$overlay ) : |
101 |
$( [] ); |
102 |
if ( this.fieldWidget.getInputId() ) { |
103 |
this.$label.attr( 'for', this.fieldWidget.getInputId() ); |
104 |
if ( this.helpInline ) { |
105 |
this.$help.attr( 'for', this.fieldWidget.getInputId() ); |
106 |
} |
107 |
} else { |
108 |
this.$label.on( 'click', function () { |
109 |
this.fieldWidget.simulateLabelClick(); |
110 |
}.bind( this ) ); |
111 |
if ( this.helpInline ) { |
112 |
this.$help.on( 'click', function () { |
113 |
this.fieldWidget.simulateLabelClick(); |
114 |
}.bind( this ) ); |
115 |
} |
116 |
} |
117 |
this.$element |
118 |
.addClass( 'oo-ui-fieldLayout' ) |
119 |
.toggleClass( 'oo-ui-fieldLayout-disabled', this.fieldWidget.isDisabled() ) |
120 |
.append( this.$body ); |
121 |
this.$body.addClass( 'oo-ui-fieldLayout-body' ); |
122 |
this.$header.addClass( 'oo-ui-fieldLayout-header' ); |
123 |
this.$messages.addClass( 'oo-ui-fieldLayout-messages' ); |
124 |
this.$field |
125 |
.addClass( 'oo-ui-fieldLayout-field' ) |
126 |
.append( this.fieldWidget.$element ); |
127 |
|
128 |
this.setErrors( config.errors || [] ); |
129 |
this.setNotices( config.notices || [] ); |
130 |
this.setAlignment( config.align ); |
131 |
// Call this again to take into account the widget's accessKey |
132 |
this.updateTitle(); |
133 |
}; |
134 |
|
135 |
/* Setup */ |
136 |
|
137 |
OO.inheritClass( OO.ui.FieldLayout, OO.ui.Layout ); |
138 |
OO.mixinClass( OO.ui.FieldLayout, OO.ui.mixin.LabelElement ); |
139 |
OO.mixinClass( OO.ui.FieldLayout, OO.ui.mixin.TitledElement ); |
140 |
|
141 |
/* Methods */ |
142 |
|
143 |
/** |
144 |
* Handle field disable events. |
145 |
* |
146 |
* @private |
147 |
* @param {boolean} value Field is disabled |
148 |
*/ |
149 |
OO.ui.FieldLayout.prototype.onFieldDisable = function ( value ) { |
150 |
this.$element.toggleClass( 'oo-ui-fieldLayout-disabled', value ); |
151 |
}; |
152 |
|
153 |
/** |
154 |
* Get the widget contained by the field. |
155 |
* |
156 |
* @return {OO.ui.Widget} Field widget |
157 |
*/ |
158 |
OO.ui.FieldLayout.prototype.getField = function () { |
159 |
return this.fieldWidget; |
160 |
}; |
161 |
|
162 |
/** |
163 |
* Return `true` if the given field widget can be used with `'inline'` alignment (see |
164 |
* #setAlignment). Return `false` if it can't or if this can't be determined. |
165 |
* |
166 |
* @return {boolean} |
167 |
*/ |
168 |
OO.ui.FieldLayout.prototype.isFieldInline = function () { |
169 |
// This is very simplistic, but should be good enough. |
170 |
return this.getField().$element.prop( 'tagName' ).toLowerCase() === 'span'; |
171 |
}; |
172 |
|
173 |
/** |
174 |
* @protected |
175 |
* @param {string} kind 'error' or 'notice' |
176 |
* @param {string|OO.ui.HtmlSnippet} text |
177 |
* @return {jQuery} |
178 |
*/ |
179 |
OO.ui.FieldLayout.prototype.makeMessage = function ( kind, text ) { |
180 |
var $listItem, $icon, message; |
181 |
$listItem = $( '<li>' ); |
182 |
if ( kind === 'error' ) { |
183 |
$icon = new OO.ui.IconWidget( { icon: 'alert', flags: [ 'warning' ] } ).$element; |
Error |
Row 184, Column 3: "Prefer setAttribute to $.attr"
jquery/no-attr
|
184 |
$listItem.attr( 'role', 'alert' ); |
185 |
} else if ( kind === 'notice' ) { |
186 |
$icon = new OO.ui.IconWidget( { icon: 'notice' } ).$element; |
187 |
} else { |
188 |
$icon = ''; |
189 |
} |
190 |
message = new OO.ui.LabelWidget( { label: text } ); |
Error |
Row 191, Column 2: "Prefer classList to $.addClass"
jquery/no-class
|
191 |
$listItem |
192 |
.append( $icon, message.$element ) |
193 |
.addClass( 'oo-ui-fieldLayout-messages-' + kind ); |
194 |
return $listItem; |
195 |
}; |
196 |
|
197 |
/** |
198 |
* Set the field alignment mode. |
199 |
* |
200 |
* @private |
201 |
* @param {string} value Alignment mode, either 'left', 'right', 'top' or 'inline' |
202 |
* @chainable |
203 |
*/ |
204 |
OO.ui.FieldLayout.prototype.setAlignment = function ( value ) { |
205 |
if ( value !== this.align ) { |
206 |
// Default to 'left' |
207 |
if ( [ 'left', 'right', 'top', 'inline' ].indexOf( value ) === -1 ) { |
208 |
value = 'left'; |
209 |
} |
210 |
// Validate |
211 |
if ( value === 'inline' && !this.isFieldInline() ) { |
212 |
value = 'top'; |
213 |
} |
214 |
// Reorder elements |
215 |
|
216 |
if ( this.helpInline ) { |
217 |
if ( value === 'top' ) { |
218 |
this.$header.append( this.$label ); |
219 |
this.$body.append( this.$header, this.$field, this.$help ); |
220 |
} else if ( value === 'inline' ) { |
221 |
this.$header.append( this.$label, this.$help ); |
222 |
this.$body.append( this.$field, this.$header ); |
223 |
} else { |
224 |
this.$header.append( this.$label, this.$help ); |
225 |
this.$body.append( this.$header, this.$field ); |
226 |
} |
227 |
} else { |
228 |
if ( value === 'top' ) { |
229 |
this.$header.append( this.$help, this.$label ); |
230 |
this.$body.append( this.$header, this.$field ); |
231 |
} else if ( value === 'inline' ) { |
232 |
this.$header.append( this.$help, this.$label ); |
233 |
this.$body.append( this.$field, this.$header ); |
234 |
} else { |
235 |
this.$header.append( this.$label ); |
236 |
this.$body.append( this.$header, this.$help, this.$field ); |
237 |
} |
238 |
} |
239 |
// Set classes. The following classes can be used here: |
240 |
// * oo-ui-fieldLayout-align-left |
241 |
// * oo-ui-fieldLayout-align-right |
242 |
// * oo-ui-fieldLayout-align-top |
243 |
// * oo-ui-fieldLayout-align-inline |
244 |
if ( this.align ) { |
245 |
this.$element.removeClass( 'oo-ui-fieldLayout-align-' + this.align ); |
246 |
} |
247 |
this.$element.addClass( 'oo-ui-fieldLayout-align-' + value ); |
248 |
this.align = value; |
249 |
} |
250 |
|
251 |
return this; |
252 |
}; |
253 |
|
254 |
/** |
255 |
* Set the list of error messages. |
256 |
* |
257 |
* @param {Array} errors Error messages about the widget, which will be displayed below the widget. |
258 |
* The array may contain strings or OO.ui.HtmlSnippet instances. |
259 |
* @chainable |
260 |
*/ |
261 |
OO.ui.FieldLayout.prototype.setErrors = function ( errors ) { |
262 |
this.errors = errors.slice(); |
263 |
this.updateMessages(); |
264 |
return this; |
265 |
}; |
266 |
|
267 |
/** |
268 |
* Set the list of notice messages. |
269 |
* |
270 |
* @param {Array} notices Notices about the widget, which will be displayed below the widget. |
271 |
* The array may contain strings or OO.ui.HtmlSnippet instances. |
272 |
* @chainable |
273 |
*/ |
274 |
OO.ui.FieldLayout.prototype.setNotices = function ( notices ) { |
275 |
this.notices = notices.slice(); |
276 |
this.updateMessages(); |
277 |
return this; |
278 |
}; |
279 |
|
280 |
/** |
281 |
* Update the rendering of error and notice messages. |
282 |
* |
283 |
* @private |
284 |
*/ |
285 |
OO.ui.FieldLayout.prototype.updateMessages = function () { |
286 |
var i; |
287 |
this.$messages.empty(); |
288 |
|
289 |
if ( this.errors.length || this.notices.length ) { |
290 |
this.$body.after( this.$messages ); |
291 |
} else { |
292 |
this.$messages.remove(); |
293 |
return; |
294 |
} |
295 |
|
296 |
for ( i = 0; i < this.notices.length; i++ ) { |
297 |
this.$messages.append( this.makeMessage( 'notice', this.notices[ i ] ) ); |
298 |
} |
299 |
for ( i = 0; i < this.errors.length; i++ ) { |
300 |
this.$messages.append( this.makeMessage( 'error', this.errors[ i ] ) ); |
301 |
} |
302 |
}; |
303 |
|
304 |
/** |
305 |
* Include information about the widget's accessKey in our title. TitledElement calls this method. |
306 |
* (This is a bit of a hack.) |
307 |
* |
308 |
* @protected |
309 |
* @param {string} title Tooltip label for 'title' attribute |
310 |
* @return {string} |
311 |
*/ |
312 |
OO.ui.FieldLayout.prototype.formatTitleWithAccessKey = function ( title ) { |
313 |
if ( this.fieldWidget && this.fieldWidget.formatTitleWithAccessKey ) { |
314 |
return this.fieldWidget.formatTitleWithAccessKey( title ); |
315 |
} |
316 |
return title; |
317 |
}; |
318 |
|
319 |
/** |
320 |
* Creates and returns the help element. Also sets the `aria-describedby` |
321 |
* attribute on the main element of the `fieldWidget`. |
322 |
* |
323 |
* @private |
324 |
* @param {string|OO.ui.HtmlSnippet} [help] Help text. |
325 |
* @param {jQuery} [$overlay] Passed to OO.ui.PopupButtonWidget for help popup. |
326 |
* @return {jQuery} The element that should become `this.$help`. |
327 |
*/ |
328 |
OO.ui.FieldLayout.prototype.createHelpElement = function ( help, $overlay ) { |
329 |
var helpId, helpWidget; |
330 |
|
331 |
if ( this.helpInline ) { |
332 |
helpWidget = new OO.ui.LabelWidget( { |
333 |
label: help, |
334 |
classes: [ 'oo-ui-inline-help' ] |
335 |
} ); |
336 |
|
337 |
helpId = helpWidget.getElementId(); |
338 |
} else { |
339 |
helpWidget = new OO.ui.PopupButtonWidget( { |
340 |
$overlay: $overlay, |
341 |
popup: { |
342 |
padded: true |
343 |
}, |
344 |
classes: [ 'oo-ui-fieldLayout-help' ], |
345 |
framed: false, |
346 |
icon: 'info', |
347 |
label: OO.ui.msg( 'ooui-field-help' ) |
348 |
} ); |
349 |
if ( help instanceof OO.ui.HtmlSnippet ) { |
350 |
helpWidget.getPopup().$body.html( help.toString() ); |
351 |
} else { |
352 |
helpWidget.getPopup().$body.text( help ); |
353 |
} |
354 |
|
355 |
helpId = helpWidget.getPopup().getBodyId(); |
356 |
} |
357 |
|
358 |
// Set the 'aria-describedby' attribute on the fieldWidget |
359 |
// Preference given to an input or a button |
360 |
( |
361 |
this.fieldWidget.$input || |
362 |
this.fieldWidget.$button || |
363 |
this.fieldWidget.$element |
364 |
).attr( 'aria-describedby', helpId ); |
365 |
|
366 |
return helpWidget.$element; |
367 |
}; |
368 |
|
|
|
/src/layouts/FieldsetLayout.js
|
0 problems
|
|
/src/layouts/FormLayout.js
|
0 problems
|
|
/src/layouts/HorizontalLayout.js
|
0 problems
|
|
/src/layouts/IndexLayout.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 123, Column 12: "Prefer closest to $.closest"
jquery/no-closest
|
Line |
Source |
1 |
/** |
2 |
* IndexLayouts contain {@link OO.ui.TabPanelLayout tab panel layouts} as well as |
3 |
* {@link OO.ui.TabSelectWidget tabs} that allow users to easily navigate through the tab panels and |
4 |
* select which one to display. By default, only one tab panel is displayed at a time. When a user |
5 |
* navigates to a new tab panel, the index layout automatically focuses on the first focusable element, |
6 |
* unless the default setting is changed. |
7 |
* |
8 |
* TODO: This class is similar to BookletLayout, we may want to refactor to reduce duplication |
9 |
* |
10 |
* @example |
11 |
* // Example of a IndexLayout that contains two TabPanelLayouts. |
12 |
* |
13 |
* function TabPanelOneLayout( name, config ) { |
14 |
* TabPanelOneLayout.parent.call( this, name, config ); |
15 |
* this.$element.append( '<p>First tab panel</p>' ); |
16 |
* } |
17 |
* OO.inheritClass( TabPanelOneLayout, OO.ui.TabPanelLayout ); |
18 |
* TabPanelOneLayout.prototype.setupTabItem = function () { |
19 |
* this.tabItem.setLabel( 'Tab panel one' ); |
20 |
* }; |
21 |
* |
22 |
* var tabPanel1 = new TabPanelOneLayout( 'one' ), |
23 |
* tabPanel2 = new OO.ui.TabPanelLayout( 'two', { label: 'Tab panel two' } ); |
24 |
* |
25 |
* tabPanel2.$element.append( '<p>Second tab panel</p>' ); |
26 |
* |
27 |
* var index = new OO.ui.IndexLayout(); |
28 |
* |
29 |
* index.addTabPanels( [ tabPanel1, tabPanel2 ] ); |
30 |
* $( 'body' ).append( index.$element ); |
31 |
* |
32 |
* @class |
33 |
* @extends OO.ui.MenuLayout |
34 |
* |
35 |
* @constructor |
36 |
* @param {Object} [config] Configuration options |
37 |
* @cfg {boolean} [continuous=false] Show all tab panels, one after another |
38 |
* @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new tab panel is displayed. Disabled on mobile. |
39 |
*/ |
40 |
OO.ui.IndexLayout = function OoUiIndexLayout( config ) { |
41 |
// Configuration initialization |
42 |
config = $.extend( {}, config, { menuPosition: 'top' } ); |
43 |
|
44 |
// Parent constructor |
45 |
OO.ui.IndexLayout.parent.call( this, config ); |
46 |
|
47 |
// Properties |
48 |
this.currentTabPanelName = null; |
49 |
this.tabPanels = {}; |
50 |
|
51 |
this.ignoreFocus = false; |
52 |
this.stackLayout = new OO.ui.StackLayout( { |
53 |
continuous: !!config.continuous, |
54 |
expanded: this.expanded |
55 |
} ); |
56 |
this.setContentPanel( this.stackLayout ); |
57 |
this.autoFocus = config.autoFocus === undefined || !!config.autoFocus; |
58 |
|
59 |
this.tabSelectWidget = new OO.ui.TabSelectWidget(); |
60 |
this.tabPanel = new OO.ui.PanelLayout( { |
61 |
expanded: this.expanded |
62 |
} ); |
63 |
this.setMenuPanel( this.tabPanel ); |
64 |
|
65 |
this.toggleMenu( true ); |
66 |
|
67 |
// Events |
68 |
this.stackLayout.connect( this, { set: 'onStackLayoutSet' } ); |
69 |
this.tabSelectWidget.connect( this, { select: 'onTabSelectWidgetSelect' } ); |
70 |
if ( this.autoFocus ) { |
71 |
// Event 'focus' does not bubble, but 'focusin' does |
72 |
this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) ); |
73 |
} |
74 |
|
75 |
// Initialization |
76 |
this.$element.addClass( 'oo-ui-indexLayout' ); |
77 |
this.stackLayout.$element.addClass( 'oo-ui-indexLayout-stackLayout' ); |
78 |
this.tabPanel.$element |
79 |
.addClass( 'oo-ui-indexLayout-tabPanel' ) |
80 |
.append( this.tabSelectWidget.$element ); |
81 |
}; |
82 |
|
83 |
/* Setup */ |
84 |
|
85 |
OO.inheritClass( OO.ui.IndexLayout, OO.ui.MenuLayout ); |
86 |
|
87 |
/* Events */ |
88 |
|
89 |
/** |
90 |
* A 'set' event is emitted when a tab panel is {@link #setTabPanel set} to be displayed by the index layout. |
91 |
* @event set |
92 |
* @param {OO.ui.TabPanelLayout} tabPanel Current tab panel |
93 |
*/ |
94 |
|
95 |
/** |
96 |
* An 'add' event is emitted when tab panels are {@link #addTabPanels added} to the index layout. |
97 |
* |
98 |
* @event add |
99 |
* @param {OO.ui.TabPanelLayout[]} tabPanel Added tab panels |
100 |
* @param {number} index Index tab panels were added at |
101 |
*/ |
102 |
|
103 |
/** |
104 |
* A 'remove' event is emitted when tab panels are {@link #clearTabPanels cleared} or |
105 |
* {@link #removeTabPanels removed} from the index. |
106 |
* |
107 |
* @event remove |
108 |
* @param {OO.ui.TabPanelLayout[]} tabPanel Removed tab panels |
109 |
*/ |
110 |
|
111 |
/* Methods */ |
112 |
|
113 |
/** |
114 |
* Handle stack layout focus. |
115 |
* |
116 |
* @private |
117 |
* @param {jQuery.Event} e Focusing event |
118 |
*/ |
119 |
OO.ui.IndexLayout.prototype.onStackLayoutFocus = function ( e ) { |
120 |
var name, $target; |
121 |
|
122 |
// Find the tab panel that an element was focused within |
Error |
Row 123, Column 12: "Prefer closest to $.closest"
jquery/no-closest
|
123 |
$target = $( e.target ).closest( '.oo-ui-tabPanelLayout' ); |
124 |
for ( name in this.tabPanels ) { |
125 |
// Check for tab panel match, exclude current tab panel to find only tab panel changes |
126 |
if ( this.tabPanels[ name ].$element[ 0 ] === $target[ 0 ] && name !== this.currentTabPanelName ) { |
127 |
this.setTabPanel( name ); |
128 |
break; |
129 |
} |
130 |
} |
131 |
}; |
132 |
|
133 |
/** |
134 |
* Handle stack layout set events. |
135 |
* |
136 |
* @private |
137 |
* @param {OO.ui.PanelLayout|null} tabPanel The tab panel that is now the current panel |
138 |
*/ |
139 |
OO.ui.IndexLayout.prototype.onStackLayoutSet = function ( tabPanel ) { |
140 |
// If everything is unselected, do nothing |
141 |
if ( !tabPanel ) { |
142 |
return; |
143 |
} |
144 |
// Focus the first element on the newly selected panel |
145 |
if ( this.autoFocus && !OO.ui.isMobile() ) { |
146 |
this.focus(); |
147 |
} |
148 |
}; |
149 |
|
150 |
/** |
151 |
* Focus the first input in the current tab panel. |
152 |
* |
153 |
* If no tab panel is selected, the first selectable tab panel will be selected. |
154 |
* If the focus is already in an element on the current tab panel, nothing will happen. |
155 |
* |
156 |
* @param {number} [itemIndex] A specific item to focus on |
157 |
*/ |
158 |
OO.ui.IndexLayout.prototype.focus = function ( itemIndex ) { |
159 |
var tabPanel, |
160 |
items = this.stackLayout.getItems(); |
161 |
|
162 |
if ( itemIndex !== undefined && items[ itemIndex ] ) { |
163 |
tabPanel = items[ itemIndex ]; |
164 |
} else { |
165 |
tabPanel = this.stackLayout.getCurrentItem(); |
166 |
} |
167 |
|
168 |
if ( !tabPanel ) { |
169 |
this.selectFirstSelectableTabPanel(); |
170 |
tabPanel = this.stackLayout.getCurrentItem(); |
171 |
} |
172 |
if ( !tabPanel ) { |
173 |
return; |
174 |
} |
175 |
// Only change the focus if is not already in the current page |
176 |
if ( !OO.ui.contains( tabPanel.$element[ 0 ], this.getElementDocument().activeElement, true ) ) { |
177 |
tabPanel.focus(); |
178 |
} |
179 |
}; |
180 |
|
181 |
/** |
182 |
* Find the first focusable input in the index layout and focus |
183 |
* on it. |
184 |
*/ |
185 |
OO.ui.IndexLayout.prototype.focusFirstFocusable = function () { |
186 |
OO.ui.findFocusable( this.stackLayout.$element ).focus(); |
187 |
}; |
188 |
|
189 |
/** |
190 |
* Handle tab widget select events. |
191 |
* |
192 |
* @private |
193 |
* @param {OO.ui.OptionWidget|null} item Selected item |
194 |
*/ |
195 |
OO.ui.IndexLayout.prototype.onTabSelectWidgetSelect = function ( item ) { |
196 |
if ( item ) { |
197 |
this.setTabPanel( item.getData() ); |
198 |
} |
199 |
}; |
200 |
|
201 |
/** |
202 |
* Get the tab panel closest to the specified tab panel. |
203 |
* |
204 |
* @param {OO.ui.TabPanelLayout} tabPanel Tab panel to use as a reference point |
205 |
* @return {OO.ui.TabPanelLayout|null} Tab panel closest to the specified |
206 |
*/ |
207 |
OO.ui.IndexLayout.prototype.getClosestTabPanel = function ( tabPanel ) { |
208 |
var next, prev, level, |
209 |
tabPanels = this.stackLayout.getItems(), |
210 |
index = tabPanels.indexOf( tabPanel ); |
211 |
|
212 |
if ( index !== -1 ) { |
213 |
next = tabPanels[ index + 1 ]; |
214 |
prev = tabPanels[ index - 1 ]; |
215 |
// Prefer adjacent tab panels at the same level |
216 |
level = this.tabSelectWidget.findItemFromData( tabPanel.getName() ).getLevel(); |
217 |
if ( |
218 |
prev && |
219 |
level === this.tabSelectWidget.findItemFromData( prev.getName() ).getLevel() |
220 |
) { |
221 |
return prev; |
222 |
} |
223 |
if ( |
224 |
next && |
225 |
level === this.tabSelectWidget.findItemFromData( next.getName() ).getLevel() |
226 |
) { |
227 |
return next; |
228 |
} |
229 |
} |
230 |
return prev || next || null; |
231 |
}; |
232 |
|
233 |
/** |
234 |
* Get the tabs widget. |
235 |
* |
236 |
* @return {OO.ui.TabSelectWidget} Tabs widget |
237 |
*/ |
238 |
OO.ui.IndexLayout.prototype.getTabs = function () { |
239 |
return this.tabSelectWidget; |
240 |
}; |
241 |
|
242 |
/** |
243 |
* Get a tab panel by its symbolic name. |
244 |
* |
245 |
* @param {string} name Symbolic name of tab panel |
246 |
* @return {OO.ui.TabPanelLayout|undefined} Tab panel, if found |
247 |
*/ |
248 |
OO.ui.IndexLayout.prototype.getTabPanel = function ( name ) { |
249 |
return this.tabPanels[ name ]; |
250 |
}; |
251 |
|
252 |
/** |
253 |
* Get the current tab panel. |
254 |
* |
255 |
* @return {OO.ui.TabPanelLayout|undefined} Current tab panel, if found |
256 |
*/ |
257 |
OO.ui.IndexLayout.prototype.getCurrentTabPanel = function () { |
258 |
var name = this.getCurrentTabPanelName(); |
259 |
return name ? this.getTabPanel( name ) : undefined; |
260 |
}; |
261 |
|
262 |
/** |
263 |
* Get the symbolic name of the current tab panel. |
264 |
* |
265 |
* @return {string|null} Symbolic name of the current tab panel |
266 |
*/ |
267 |
OO.ui.IndexLayout.prototype.getCurrentTabPanelName = function () { |
268 |
return this.currentTabPanelName; |
269 |
}; |
270 |
|
271 |
/** |
272 |
* Add tab panels to the index layout |
273 |
* |
274 |
* When tab panels are added with the same names as existing tab panels, the existing tab panels |
275 |
* will be automatically removed before the new tab panels are added. |
276 |
* |
277 |
* @param {OO.ui.TabPanelLayout[]} tabPanels Tab panels to add |
278 |
* @param {number} index Index of the insertion point |
279 |
* @fires add |
280 |
* @chainable |
281 |
*/ |
282 |
OO.ui.IndexLayout.prototype.addTabPanels = function ( tabPanels, index ) { |
283 |
var i, len, name, tabPanel, item, currentIndex, |
284 |
stackLayoutTabPanels = this.stackLayout.getItems(), |
285 |
remove = [], |
286 |
items = []; |
287 |
|
288 |
// Remove tab panels with same names |
289 |
for ( i = 0, len = tabPanels.length; i < len; i++ ) { |
290 |
tabPanel = tabPanels[ i ]; |
291 |
name = tabPanel.getName(); |
292 |
|
293 |
if ( Object.prototype.hasOwnProperty.call( this.tabPanels, name ) ) { |
294 |
// Correct the insertion index |
295 |
currentIndex = stackLayoutTabPanels.indexOf( this.tabPanels[ name ] ); |
296 |
if ( currentIndex !== -1 && currentIndex + 1 < index ) { |
297 |
index--; |
298 |
} |
299 |
remove.push( this.tabPanels[ name ] ); |
300 |
} |
301 |
} |
302 |
if ( remove.length ) { |
303 |
this.removeTabPanels( remove ); |
304 |
} |
305 |
|
306 |
// Add new tab panels |
307 |
for ( i = 0, len = tabPanels.length; i < len; i++ ) { |
308 |
tabPanel = tabPanels[ i ]; |
309 |
name = tabPanel.getName(); |
310 |
this.tabPanels[ tabPanel.getName() ] = tabPanel; |
311 |
item = new OO.ui.TabOptionWidget( { data: name } ); |
312 |
tabPanel.setTabItem( item ); |
313 |
items.push( item ); |
314 |
} |
315 |
|
316 |
if ( items.length ) { |
317 |
this.tabSelectWidget.addItems( items, index ); |
318 |
this.selectFirstSelectableTabPanel(); |
319 |
} |
320 |
this.stackLayout.addItems( tabPanels, index ); |
321 |
this.emit( 'add', tabPanels, index ); |
322 |
|
323 |
return this; |
324 |
}; |
325 |
|
326 |
/** |
327 |
* Remove the specified tab panels from the index layout. |
328 |
* |
329 |
* To remove all tab panels from the index, you may wish to use the #clearTabPanels method instead. |
330 |
* |
331 |
* @param {OO.ui.TabPanelLayout[]} tabPanels An array of tab panels to remove |
332 |
* @fires remove |
333 |
* @chainable |
334 |
*/ |
335 |
OO.ui.IndexLayout.prototype.removeTabPanels = function ( tabPanels ) { |
336 |
var i, len, name, tabPanel, |
337 |
items = []; |
338 |
|
339 |
for ( i = 0, len = tabPanels.length; i < len; i++ ) { |
340 |
tabPanel = tabPanels[ i ]; |
341 |
name = tabPanel.getName(); |
342 |
delete this.tabPanels[ name ]; |
343 |
items.push( this.tabSelectWidget.findItemFromData( name ) ); |
344 |
tabPanel.setTabItem( null ); |
345 |
} |
346 |
if ( items.length ) { |
347 |
this.tabSelectWidget.removeItems( items ); |
348 |
this.selectFirstSelectableTabPanel(); |
349 |
} |
350 |
this.stackLayout.removeItems( tabPanels ); |
351 |
this.emit( 'remove', tabPanels ); |
352 |
|
353 |
return this; |
354 |
}; |
355 |
|
356 |
/** |
357 |
* Clear all tab panels from the index layout. |
358 |
* |
359 |
* To remove only a subset of tab panels from the index, use the #removeTabPanels method. |
360 |
* |
361 |
* @fires remove |
362 |
* @chainable |
363 |
*/ |
364 |
OO.ui.IndexLayout.prototype.clearTabPanels = function () { |
365 |
var i, len, |
366 |
tabPanels = this.stackLayout.getItems(); |
367 |
|
368 |
this.tabPanels = {}; |
369 |
this.currentTabPanelName = null; |
370 |
this.tabSelectWidget.clearItems(); |
371 |
for ( i = 0, len = tabPanels.length; i < len; i++ ) { |
372 |
tabPanels[ i ].setTabItem( null ); |
373 |
} |
374 |
this.stackLayout.clearItems(); |
375 |
|
376 |
this.emit( 'remove', tabPanels ); |
377 |
|
378 |
return this; |
379 |
}; |
380 |
|
381 |
/** |
382 |
* Set the current tab panel by symbolic name. |
383 |
* |
384 |
* @fires set |
385 |
* @param {string} name Symbolic name of tab panel |
386 |
*/ |
387 |
OO.ui.IndexLayout.prototype.setTabPanel = function ( name ) { |
388 |
var selectedItem, |
389 |
$focused, |
390 |
previousTabPanel, |
391 |
tabPanel = this.tabPanels[ name ]; |
392 |
|
393 |
if ( name !== this.currentTabPanelName ) { |
394 |
previousTabPanel = this.getCurrentTabPanel(); |
395 |
selectedItem = this.tabSelectWidget.findSelectedItem(); |
396 |
if ( selectedItem && selectedItem.getData() !== name ) { |
397 |
this.tabSelectWidget.selectItemByData( name ); |
398 |
} |
399 |
if ( tabPanel ) { |
400 |
if ( previousTabPanel ) { |
401 |
previousTabPanel.setActive( false ); |
402 |
// Blur anything focused if the next tab panel doesn't have anything focusable. |
403 |
// This is not needed if the next tab panel has something focusable (because once it is focused |
404 |
// this blur happens automatically). If the layout is non-continuous, this check is |
405 |
// meaningless because the next tab panel is not visible yet and thus can't hold focus. |
406 |
if ( |
407 |
this.autoFocus && |
408 |
!OO.ui.isMobile() && |
409 |
this.stackLayout.continuous && |
410 |
OO.ui.findFocusable( tabPanel.$element ).length !== 0 |
411 |
) { |
412 |
$focused = previousTabPanel.$element.find( ':focus' ); |
413 |
if ( $focused.length ) { |
414 |
$focused[ 0 ].blur(); |
415 |
} |
416 |
} |
417 |
} |
418 |
this.currentTabPanelName = name; |
419 |
tabPanel.setActive( true ); |
420 |
this.stackLayout.setItem( tabPanel ); |
421 |
if ( !this.stackLayout.continuous && previousTabPanel ) { |
422 |
// This should not be necessary, since any inputs on the previous tab panel should have been |
423 |
// blurred when it was hidden, but browsers are not very consistent about this. |
424 |
$focused = previousTabPanel.$element.find( ':focus' ); |
425 |
if ( $focused.length ) { |
426 |
$focused[ 0 ].blur(); |
427 |
} |
428 |
} |
429 |
this.emit( 'set', tabPanel ); |
430 |
} |
431 |
} |
432 |
}; |
433 |
|
434 |
/** |
435 |
* Select the first selectable tab panel. |
436 |
* |
437 |
* @chainable |
438 |
*/ |
439 |
OO.ui.IndexLayout.prototype.selectFirstSelectableTabPanel = function () { |
440 |
if ( !this.tabSelectWidget.findSelectedItem() ) { |
441 |
this.tabSelectWidget.selectItem( this.tabSelectWidget.findFirstSelectableItem() ); |
442 |
} |
443 |
|
444 |
return this; |
445 |
}; |
446 |
|
|
|
/src/layouts/PageLayout.js
|
0 problems
|
|
/src/layouts/PanelLayout.js
|
0 problems
|
|
/src/layouts/StackLayout.js
|
0 problems
|
|
/src/layouts/TabPanelLayout.js
|
0 problems
|
|
/src/mixin.js
|
0 problems
|
|
/src/mixins/AccessKeyedElement.js
|
0 problems
|
|
/src/mixins/ButtonElement.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 90, Column 17: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* ButtonElement is often mixed into other classes to generate a button, which is a clickable |
3 |
* interface element that can be configured with access keys for accessibility. |
4 |
* See the [OOUI documentation on MediaWiki] [1] for examples. |
5 |
* |
6 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Buttons_and_Switches#Buttons |
7 |
* |
8 |
* @abstract |
9 |
* @class |
10 |
* |
11 |
* @constructor |
12 |
* @param {Object} [config] Configuration options |
13 |
* @cfg {jQuery} [$button] The button element created by the class. |
14 |
* If this configuration is omitted, the button element will use a generated `<a>`. |
15 |
* @cfg {boolean} [framed=true] Render the button with a frame |
16 |
*/ |
17 |
OO.ui.mixin.ButtonElement = function OoUiMixinButtonElement( config ) { |
18 |
// Configuration initialization |
19 |
config = config || {}; |
20 |
|
21 |
// Properties |
22 |
this.$button = null; |
23 |
this.framed = null; |
24 |
this.active = config.active !== undefined && config.active; |
25 |
this.onDocumentMouseUpHandler = this.onDocumentMouseUp.bind( this ); |
26 |
this.onMouseDownHandler = this.onMouseDown.bind( this ); |
27 |
this.onDocumentKeyUpHandler = this.onDocumentKeyUp.bind( this ); |
28 |
this.onKeyDownHandler = this.onKeyDown.bind( this ); |
29 |
this.onClickHandler = this.onClick.bind( this ); |
30 |
this.onKeyPressHandler = this.onKeyPress.bind( this ); |
31 |
|
32 |
// Initialization |
33 |
this.$element.addClass( 'oo-ui-buttonElement' ); |
34 |
this.toggleFramed( config.framed === undefined || config.framed ); |
35 |
this.setButtonElement( config.$button || $( '<a>' ) ); |
36 |
}; |
37 |
|
38 |
/* Setup */ |
39 |
|
40 |
OO.initClass( OO.ui.mixin.ButtonElement ); |
41 |
|
42 |
/* Static Properties */ |
43 |
|
44 |
/** |
45 |
* Cancel mouse down events. |
46 |
* |
47 |
* This property is usually set to `true` to prevent the focus from changing when the button is clicked. |
48 |
* Classes such as {@link OO.ui.mixin.DraggableElement DraggableElement} and {@link OO.ui.ButtonOptionWidget ButtonOptionWidget} |
49 |
* use a value of `false` so that dragging behavior is possible and mousedown events can be handled by a |
50 |
* parent widget. |
51 |
* |
52 |
* @static |
53 |
* @inheritable |
54 |
* @property {boolean} |
55 |
*/ |
56 |
OO.ui.mixin.ButtonElement.static.cancelButtonMouseDownEvents = true; |
57 |
|
58 |
/* Events */ |
59 |
|
60 |
/** |
61 |
* A 'click' event is emitted when the button element is clicked. |
62 |
* |
63 |
* @event click |
64 |
*/ |
65 |
|
66 |
/* Methods */ |
67 |
|
68 |
/** |
69 |
* Set the button element. |
70 |
* |
71 |
* This method is used to retarget a button mixin so that its functionality applies to |
72 |
* the specified button element instead of the one created by the class. If a button element |
73 |
* is already set, the method will remove the mixin’s effect on that element. |
74 |
* |
75 |
* @param {jQuery} $button Element to use as button |
76 |
*/ |
77 |
OO.ui.mixin.ButtonElement.prototype.setButtonElement = function ( $button ) { |
78 |
if ( this.$button ) { |
79 |
this.$button |
80 |
.removeClass( 'oo-ui-buttonElement-button' ) |
81 |
.removeAttr( 'role accesskey' ) |
82 |
.off( { |
83 |
mousedown: this.onMouseDownHandler, |
84 |
keydown: this.onKeyDownHandler, |
85 |
click: this.onClickHandler, |
86 |
keypress: this.onKeyPressHandler |
87 |
} ); |
88 |
} |
89 |
|
Error |
Row 90, Column 17: "Prefer classList to $.addClass"
jquery/no-class
|
90 |
this.$button = $button |
91 |
.addClass( 'oo-ui-buttonElement-button' ) |
92 |
.on( { |
93 |
mousedown: this.onMouseDownHandler, |
94 |
keydown: this.onKeyDownHandler, |
95 |
click: this.onClickHandler, |
96 |
keypress: this.onKeyPressHandler |
97 |
} ); |
98 |
|
99 |
// Add `role="button"` on `<a>` elements, where it's needed |
100 |
// `toUpperCase()` is added for XHTML documents |
101 |
if ( this.$button.prop( 'tagName' ).toUpperCase() === 'A' ) { |
102 |
this.$button.attr( 'role', 'button' ); |
103 |
} |
104 |
}; |
105 |
|
106 |
/** |
107 |
* Handles mouse down events. |
108 |
* |
109 |
* @protected |
110 |
* @param {jQuery.Event} e Mouse down event |
111 |
*/ |
112 |
OO.ui.mixin.ButtonElement.prototype.onMouseDown = function ( e ) { |
113 |
if ( this.isDisabled() || e.which !== OO.ui.MouseButtons.LEFT ) { |
114 |
return; |
115 |
} |
116 |
this.$element.addClass( 'oo-ui-buttonElement-pressed' ); |
117 |
// Run the mouseup handler no matter where the mouse is when the button is let go, so we can |
118 |
// reliably remove the pressed class |
119 |
this.getElementDocument().addEventListener( 'mouseup', this.onDocumentMouseUpHandler, true ); |
120 |
// Prevent change of focus unless specifically configured otherwise |
121 |
if ( this.constructor.static.cancelButtonMouseDownEvents ) { |
122 |
return false; |
123 |
} |
124 |
}; |
125 |
|
126 |
/** |
127 |
* Handles document mouse up events. |
128 |
* |
129 |
* @protected |
130 |
* @param {MouseEvent} e Mouse up event |
131 |
*/ |
132 |
OO.ui.mixin.ButtonElement.prototype.onDocumentMouseUp = function ( e ) { |
133 |
if ( this.isDisabled() || e.which !== OO.ui.MouseButtons.LEFT ) { |
134 |
return; |
135 |
} |
136 |
this.$element.removeClass( 'oo-ui-buttonElement-pressed' ); |
137 |
// Stop listening for mouseup, since we only needed this once |
138 |
this.getElementDocument().removeEventListener( 'mouseup', this.onDocumentMouseUpHandler, true ); |
139 |
}; |
140 |
|
141 |
// Deprecated alias since 0.28.3 |
142 |
OO.ui.mixin.ButtonElement.prototype.onMouseUp = function () { |
143 |
OO.ui.warnDeprecation( 'onMouseUp is deprecated, use onDocumentMouseUp instead' ); |
144 |
this.onDocumentMouseUp.apply( this, arguments ); |
145 |
}; |
146 |
|
147 |
/** |
148 |
* Handles mouse click events. |
149 |
* |
150 |
* @protected |
151 |
* @param {jQuery.Event} e Mouse click event |
152 |
* @fires click |
153 |
*/ |
154 |
OO.ui.mixin.ButtonElement.prototype.onClick = function ( e ) { |
155 |
if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) { |
156 |
if ( this.emit( 'click' ) ) { |
157 |
return false; |
158 |
} |
159 |
} |
160 |
}; |
161 |
|
162 |
/** |
163 |
* Handles key down events. |
164 |
* |
165 |
* @protected |
166 |
* @param {jQuery.Event} e Key down event |
167 |
*/ |
168 |
OO.ui.mixin.ButtonElement.prototype.onKeyDown = function ( e ) { |
169 |
if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) { |
170 |
return; |
171 |
} |
172 |
this.$element.addClass( 'oo-ui-buttonElement-pressed' ); |
173 |
// Run the keyup handler no matter where the key is when the button is let go, so we can |
174 |
// reliably remove the pressed class |
175 |
this.getElementDocument().addEventListener( 'keyup', this.onDocumentKeyUpHandler, true ); |
176 |
}; |
177 |
|
178 |
/** |
179 |
* Handles document key up events. |
180 |
* |
181 |
* @protected |
182 |
* @param {KeyboardEvent} e Key up event |
183 |
*/ |
184 |
OO.ui.mixin.ButtonElement.prototype.onDocumentKeyUp = function ( e ) { |
185 |
if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) { |
186 |
return; |
187 |
} |
188 |
this.$element.removeClass( 'oo-ui-buttonElement-pressed' ); |
189 |
// Stop listening for keyup, since we only needed this once |
190 |
this.getElementDocument().removeEventListener( 'keyup', this.onDocumentKeyUpHandler, true ); |
191 |
}; |
192 |
|
193 |
// Deprecated alias since 0.28.3 |
194 |
OO.ui.mixin.ButtonElement.prototype.onKeyUp = function () { |
195 |
OO.ui.warnDeprecation( 'onKeyUp is deprecated, use onDocumentKeyUp instead' ); |
196 |
this.onDocumentKeyUp.apply( this, arguments ); |
197 |
}; |
198 |
|
199 |
/** |
200 |
* Handles key press events. |
201 |
* |
202 |
* @protected |
203 |
* @param {jQuery.Event} e Key press event |
204 |
* @fires click |
205 |
*/ |
206 |
OO.ui.mixin.ButtonElement.prototype.onKeyPress = function ( e ) { |
207 |
if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) { |
208 |
if ( this.emit( 'click' ) ) { |
209 |
return false; |
210 |
} |
211 |
} |
212 |
}; |
213 |
|
214 |
/** |
215 |
* Check if button has a frame. |
216 |
* |
217 |
* @return {boolean} Button is framed |
218 |
*/ |
219 |
OO.ui.mixin.ButtonElement.prototype.isFramed = function () { |
220 |
return this.framed; |
221 |
}; |
222 |
|
223 |
/** |
224 |
* Render the button with or without a frame. Omit the `framed` parameter to toggle the button frame on and off. |
225 |
* |
226 |
* @param {boolean} [framed] Make button framed, omit to toggle |
227 |
* @chainable |
228 |
*/ |
229 |
OO.ui.mixin.ButtonElement.prototype.toggleFramed = function ( framed ) { |
230 |
framed = framed === undefined ? !this.framed : !!framed; |
231 |
if ( framed !== this.framed ) { |
232 |
this.framed = framed; |
233 |
this.$element |
234 |
.toggleClass( 'oo-ui-buttonElement-frameless', !framed ) |
235 |
.toggleClass( 'oo-ui-buttonElement-framed', framed ); |
236 |
this.updateThemeClasses(); |
237 |
} |
238 |
|
239 |
return this; |
240 |
}; |
241 |
|
242 |
/** |
243 |
* Set the button's active state. |
244 |
* |
245 |
* The active state can be set on: |
246 |
* |
247 |
* - {@link OO.ui.ButtonOptionWidget ButtonOptionWidget} when it is selected |
248 |
* - {@link OO.ui.ToggleButtonWidget ToggleButtonWidget} when it is toggle on |
249 |
* - {@link OO.ui.ButtonWidget ButtonWidget} when clicking the button would only refresh the page |
250 |
* |
251 |
* @protected |
252 |
* @param {boolean} value Make button active |
253 |
* @chainable |
254 |
*/ |
255 |
OO.ui.mixin.ButtonElement.prototype.setActive = function ( value ) { |
256 |
this.active = !!value; |
257 |
this.$element.toggleClass( 'oo-ui-buttonElement-active', this.active ); |
258 |
this.updateThemeClasses(); |
259 |
return this; |
260 |
}; |
261 |
|
262 |
/** |
263 |
* Check if the button is active |
264 |
* |
265 |
* @protected |
266 |
* @return {boolean} The button is active |
267 |
*/ |
268 |
OO.ui.mixin.ButtonElement.prototype.isActive = function () { |
269 |
return this.active; |
270 |
}; |
271 |
|
|
|
/src/mixins/ClippableElement.js
|
4 problems (4 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 65, Column 20: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 296, Column 14: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 297, Column 48: "Prefer direct property access to $.prop"
jquery/no-prop
|
Error |
Row 298, Column 51: "Prefer direct property access to $.prop"
jquery/no-prop
|
Line |
Source |
1 |
/** |
2 |
* Element that can be automatically clipped to visible boundaries. |
3 |
* |
4 |
* Whenever the element's natural height changes, you have to call |
5 |
* {@link OO.ui.mixin.ClippableElement#clip} to make sure it's still |
6 |
* clipping correctly. |
7 |
* |
8 |
* The dimensions of #$clippableContainer will be compared to the boundaries of the |
9 |
* nearest scrollable container. If #$clippableContainer is too tall and/or too wide, |
10 |
* then #$clippable will be given a fixed reduced height and/or width and will be made |
11 |
* scrollable. By default, #$clippable and #$clippableContainer are the same element, |
12 |
* but you can build a static footer by setting #$clippableContainer to an element that contains |
13 |
* #$clippable and the footer. |
14 |
* |
15 |
* @abstract |
16 |
* @class |
17 |
* |
18 |
* @constructor |
19 |
* @param {Object} [config] Configuration options |
20 |
* @cfg {jQuery} [$clippable] Node to clip, assigned to #$clippable, omit to use #$element |
21 |
* @cfg {jQuery} [$clippableContainer] Node to keep visible, assigned to #$clippableContainer, |
22 |
* omit to use #$clippable |
23 |
*/ |
24 |
OO.ui.mixin.ClippableElement = function OoUiMixinClippableElement( config ) { |
25 |
// Configuration initialization |
26 |
config = config || {}; |
27 |
|
28 |
// Properties |
29 |
this.$clippable = null; |
30 |
this.$clippableContainer = null; |
31 |
this.clipping = false; |
32 |
this.clippedHorizontally = false; |
33 |
this.clippedVertically = false; |
34 |
this.$clippableScrollableContainer = null; |
35 |
this.$clippableScroller = null; |
36 |
this.$clippableWindow = null; |
37 |
this.idealWidth = null; |
38 |
this.idealHeight = null; |
39 |
this.onClippableScrollHandler = this.clip.bind( this ); |
40 |
this.onClippableWindowResizeHandler = this.clip.bind( this ); |
41 |
|
42 |
// Initialization |
43 |
if ( config.$clippableContainer ) { |
44 |
this.setClippableContainer( config.$clippableContainer ); |
45 |
} |
46 |
this.setClippableElement( config.$clippable || this.$element ); |
47 |
}; |
48 |
|
49 |
/* Methods */ |
50 |
|
51 |
/** |
52 |
* Set clippable element. |
53 |
* |
54 |
* If an element is already set, it will be cleaned up before setting up the new element. |
55 |
* |
56 |
* @param {jQuery} $clippable Element to make clippable |
57 |
*/ |
58 |
OO.ui.mixin.ClippableElement.prototype.setClippableElement = function ( $clippable ) { |
59 |
if ( this.$clippable ) { |
60 |
this.$clippable.removeClass( 'oo-ui-clippableElement-clippable' ); |
61 |
this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } ); |
62 |
OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] ); |
63 |
} |
64 |
|
Error |
Row 65, Column 20: "Prefer classList to $.addClass"
jquery/no-class
|
65 |
this.$clippable = $clippable.addClass( 'oo-ui-clippableElement-clippable' ); |
66 |
this.clip(); |
67 |
}; |
68 |
|
69 |
/** |
70 |
* Set clippable container. |
71 |
* |
72 |
* This is the container that will be measured when deciding whether to clip. When clipping, |
73 |
* #$clippable will be resized in order to keep the clippable container fully visible. |
74 |
* |
75 |
* If the clippable container is unset, #$clippable will be used. |
76 |
* |
77 |
* @param {jQuery|null} $clippableContainer Container to keep visible, or null to unset |
78 |
*/ |
79 |
OO.ui.mixin.ClippableElement.prototype.setClippableContainer = function ( $clippableContainer ) { |
80 |
this.$clippableContainer = $clippableContainer; |
81 |
if ( this.$clippable ) { |
82 |
this.clip(); |
83 |
} |
84 |
}; |
85 |
|
86 |
/** |
87 |
* Toggle clipping. |
88 |
* |
89 |
* Do not turn clipping on until after the element is attached to the DOM and visible. |
90 |
* |
91 |
* @param {boolean} [clipping] Enable clipping, omit to toggle |
92 |
* @chainable |
93 |
*/ |
94 |
OO.ui.mixin.ClippableElement.prototype.toggleClipping = function ( clipping ) { |
95 |
clipping = clipping === undefined ? !this.clipping : !!clipping; |
96 |
|
97 |
if ( clipping && !this.warnedUnattached && !this.isElementAttached() ) { |
98 |
OO.ui.warnDeprecation( 'ClippableElement#toggleClipping: Before calling this method, the element must be attached to the DOM.' ); |
99 |
this.warnedUnattached = true; |
100 |
} |
101 |
|
102 |
if ( this.clipping !== clipping ) { |
103 |
this.clipping = clipping; |
104 |
if ( clipping ) { |
105 |
this.$clippableScrollableContainer = $( this.getClosestScrollableElementContainer() ); |
106 |
// If the clippable container is the root, we have to listen to scroll events and check |
107 |
// jQuery.scrollTop on the window because of browser inconsistencies |
108 |
this.$clippableScroller = this.$clippableScrollableContainer.is( 'html, body' ) ? |
109 |
$( OO.ui.Element.static.getWindow( this.$clippableScrollableContainer ) ) : |
110 |
this.$clippableScrollableContainer; |
111 |
this.$clippableScroller.on( 'scroll', this.onClippableScrollHandler ); |
112 |
this.$clippableWindow = $( this.getElementWindow() ) |
113 |
.on( 'resize', this.onClippableWindowResizeHandler ); |
114 |
// Initial clip after visible |
115 |
this.clip(); |
116 |
} else { |
117 |
this.$clippable.css( { |
118 |
width: '', |
119 |
height: '', |
120 |
maxWidth: '', |
121 |
maxHeight: '', |
122 |
overflowX: '', |
123 |
overflowY: '' |
124 |
} ); |
125 |
OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] ); |
126 |
|
127 |
this.$clippableScrollableContainer = null; |
128 |
this.$clippableScroller.off( 'scroll', this.onClippableScrollHandler ); |
129 |
this.$clippableScroller = null; |
130 |
this.$clippableWindow.off( 'resize', this.onClippableWindowResizeHandler ); |
131 |
this.$clippableWindow = null; |
132 |
} |
133 |
} |
134 |
|
135 |
return this; |
136 |
}; |
137 |
|
138 |
/** |
139 |
* Check if the element will be clipped to fit the visible area of the nearest scrollable container. |
140 |
* |
141 |
* @return {boolean} Element will be clipped to the visible area |
142 |
*/ |
143 |
OO.ui.mixin.ClippableElement.prototype.isClipping = function () { |
144 |
return this.clipping; |
145 |
}; |
146 |
|
147 |
/** |
148 |
* Check if the bottom or right of the element is being clipped by the nearest scrollable container. |
149 |
* |
150 |
* @return {boolean} Part of the element is being clipped |
151 |
*/ |
152 |
OO.ui.mixin.ClippableElement.prototype.isClipped = function () { |
153 |
return this.clippedHorizontally || this.clippedVertically; |
154 |
}; |
155 |
|
156 |
/** |
157 |
* Check if the right of the element is being clipped by the nearest scrollable container. |
158 |
* |
159 |
* @return {boolean} Part of the element is being clipped |
160 |
*/ |
161 |
OO.ui.mixin.ClippableElement.prototype.isClippedHorizontally = function () { |
162 |
return this.clippedHorizontally; |
163 |
}; |
164 |
|
165 |
/** |
166 |
* Check if the bottom of the element is being clipped by the nearest scrollable container. |
167 |
* |
168 |
* @return {boolean} Part of the element is being clipped |
169 |
*/ |
170 |
OO.ui.mixin.ClippableElement.prototype.isClippedVertically = function () { |
171 |
return this.clippedVertically; |
172 |
}; |
173 |
|
174 |
/** |
175 |
* Set the ideal size. These are the dimensions #$clippable will have when it's not being clipped. |
176 |
* |
177 |
* @param {number|string} [width] Width as a number of pixels or CSS string with unit suffix |
178 |
* @param {number|string} [height] Height as a number of pixels or CSS string with unit suffix |
179 |
*/ |
180 |
OO.ui.mixin.ClippableElement.prototype.setIdealSize = function ( width, height ) { |
181 |
this.idealWidth = width; |
182 |
this.idealHeight = height; |
183 |
|
184 |
if ( !this.clipping ) { |
185 |
// Update dimensions |
186 |
this.$clippable.css( { width: width, height: height } ); |
187 |
} |
188 |
// While clipping, idealWidth and idealHeight are not considered |
189 |
}; |
190 |
|
191 |
/** |
192 |
* Return the side of the clippable on which it is "anchored" (aligned to something else). |
193 |
* ClippableElement will clip the opposite side when reducing element's width. |
194 |
* |
195 |
* Classes that mix in ClippableElement should override this to return 'right' if their |
196 |
* clippable is absolutely positioned and using 'right: Npx' (and not using 'left'). |
197 |
* If your class also mixes in FloatableElement, this is handled automatically. |
198 |
* |
199 |
* (This can't be guessed from the actual CSS because the computed values for 'left'/'right' are |
200 |
* always in pixels, even if they were unset or set to 'auto'.) |
201 |
* |
202 |
* When in doubt, 'left' (or 'right' in RTL) is a sane fallback. |
203 |
* |
204 |
* @return {string} 'left' or 'right' |
205 |
*/ |
206 |
OO.ui.mixin.ClippableElement.prototype.getHorizontalAnchorEdge = function () { |
207 |
if ( this.computePosition && this.positioning && this.computePosition().right !== '' ) { |
208 |
return 'right'; |
209 |
} |
210 |
return 'left'; |
211 |
}; |
212 |
|
213 |
/** |
214 |
* Return the side of the clippable on which it is "anchored" (aligned to something else). |
215 |
* ClippableElement will clip the opposite side when reducing element's width. |
216 |
* |
217 |
* Classes that mix in ClippableElement should override this to return 'bottom' if their |
218 |
* clippable is absolutely positioned and using 'bottom: Npx' (and not using 'top'). |
219 |
* If your class also mixes in FloatableElement, this is handled automatically. |
220 |
* |
221 |
* (This can't be guessed from the actual CSS because the computed values for 'left'/'right' are |
222 |
* always in pixels, even if they were unset or set to 'auto'.) |
223 |
* |
224 |
* When in doubt, 'top' is a sane fallback. |
225 |
* |
226 |
* @return {string} 'top' or 'bottom' |
227 |
*/ |
228 |
OO.ui.mixin.ClippableElement.prototype.getVerticalAnchorEdge = function () { |
229 |
if ( this.computePosition && this.positioning && this.computePosition().bottom !== '' ) { |
230 |
return 'bottom'; |
231 |
} |
232 |
return 'top'; |
233 |
}; |
234 |
|
235 |
/** |
236 |
* Clip element to visible boundaries and allow scrolling when needed. You should call this method |
237 |
* when the element's natural height changes. |
238 |
* |
239 |
* Element will be clipped the bottom or right of the element is within 10px of the edge of, or |
240 |
* overlapped by, the visible area of the nearest scrollable container. |
241 |
* |
242 |
* Because calling clip() when the natural height changes isn't always possible, we also set |
243 |
* max-height when the element isn't being clipped. This means that if the element tries to grow |
244 |
* beyond the edge, something reasonable will happen before clip() is called. |
245 |
* |
246 |
* @chainable |
247 |
*/ |
248 |
OO.ui.mixin.ClippableElement.prototype.clip = function () { |
249 |
var extraHeight, extraWidth, viewportSpacing, |
250 |
desiredWidth, desiredHeight, allotedWidth, allotedHeight, |
251 |
naturalWidth, naturalHeight, clipWidth, clipHeight, |
252 |
$item, itemRect, $viewport, viewportRect, availableRect, |
253 |
direction, vertScrollbarWidth, horizScrollbarHeight, |
254 |
// Extra tolerance so that the sloppy code below doesn't result in results that are off |
255 |
// by one or two pixels. (And also so that we have space to display drop shadows.) |
256 |
// Chosen by fair dice roll. |
257 |
buffer = 7; |
258 |
|
259 |
if ( !this.clipping ) { |
260 |
// this.$clippableScrollableContainer and this.$clippableWindow are null, so the below will fail |
261 |
return this; |
262 |
} |
263 |
|
264 |
function rectIntersection( a, b ) { |
265 |
var out = {}; |
266 |
out.top = Math.max( a.top, b.top ); |
267 |
out.left = Math.max( a.left, b.left ); |
268 |
out.bottom = Math.min( a.bottom, b.bottom ); |
269 |
out.right = Math.min( a.right, b.right ); |
270 |
return out; |
271 |
} |
272 |
|
273 |
viewportSpacing = OO.ui.getViewportSpacing(); |
274 |
|
275 |
if ( this.$clippableScrollableContainer.is( 'html, body' ) ) { |
276 |
$viewport = $( this.$clippableScrollableContainer[ 0 ].ownerDocument.body ); |
277 |
// Dimensions of the browser window, rather than the element! |
278 |
viewportRect = { |
279 |
top: 0, |
280 |
left: 0, |
281 |
right: document.documentElement.clientWidth, |
282 |
bottom: document.documentElement.clientHeight |
283 |
}; |
284 |
viewportRect.top += viewportSpacing.top; |
285 |
viewportRect.left += viewportSpacing.left; |
286 |
viewportRect.right -= viewportSpacing.right; |
287 |
viewportRect.bottom -= viewportSpacing.bottom; |
288 |
} else { |
289 |
$viewport = this.$clippableScrollableContainer; |
290 |
viewportRect = $viewport[ 0 ].getBoundingClientRect(); |
291 |
// Convert into a plain object |
292 |
viewportRect = $.extend( {}, viewportRect ); |
293 |
} |
294 |
|
295 |
// Account for scrollbar gutter |
Error |
Row 296, Column 14: "Prefer getComputedStyle to $.css"
jquery/no-css
|
296 |
direction = $viewport.css( 'direction' ); |
Error |
Row 297, Column 48: "Prefer direct property access to $.prop"
jquery/no-prop
|
297 |
vertScrollbarWidth = $viewport.innerWidth() - $viewport.prop( 'clientWidth' ); |
Error |
Row 298, Column 51: "Prefer direct property access to $.prop"
jquery/no-prop
|
298 |
horizScrollbarHeight = $viewport.innerHeight() - $viewport.prop( 'clientHeight' ); |
299 |
viewportRect.bottom -= horizScrollbarHeight; |
300 |
if ( direction === 'rtl' ) { |
301 |
viewportRect.left += vertScrollbarWidth; |
302 |
} else { |
303 |
viewportRect.right -= vertScrollbarWidth; |
304 |
} |
305 |
|
306 |
// Add arbitrary tolerance |
307 |
viewportRect.top += buffer; |
308 |
viewportRect.left += buffer; |
309 |
viewportRect.right -= buffer; |
310 |
viewportRect.bottom -= buffer; |
311 |
|
312 |
$item = this.$clippableContainer || this.$clippable; |
313 |
|
314 |
extraHeight = $item.outerHeight() - this.$clippable.outerHeight(); |
315 |
extraWidth = $item.outerWidth() - this.$clippable.outerWidth(); |
316 |
|
317 |
itemRect = $item[ 0 ].getBoundingClientRect(); |
318 |
// Convert into a plain object |
319 |
itemRect = $.extend( {}, itemRect ); |
320 |
|
321 |
// Item might already be clipped, so we can't just use its dimensions (in case we might need to |
322 |
// make it larger than before). Extend the rectangle to the maximum size we are allowed to take. |
323 |
if ( this.getHorizontalAnchorEdge() === 'right' ) { |
324 |
itemRect.left = viewportRect.left; |
325 |
} else { |
326 |
itemRect.right = viewportRect.right; |
327 |
} |
328 |
if ( this.getVerticalAnchorEdge() === 'bottom' ) { |
329 |
itemRect.top = viewportRect.top; |
330 |
} else { |
331 |
itemRect.bottom = viewportRect.bottom; |
332 |
} |
333 |
|
334 |
availableRect = rectIntersection( viewportRect, itemRect ); |
335 |
|
336 |
desiredWidth = Math.max( 0, availableRect.right - availableRect.left ); |
337 |
desiredHeight = Math.max( 0, availableRect.bottom - availableRect.top ); |
338 |
// It should never be desirable to exceed the dimensions of the browser viewport... right? |
339 |
desiredWidth = Math.min( desiredWidth, |
340 |
document.documentElement.clientWidth - viewportSpacing.left - viewportSpacing.right ); |
341 |
desiredHeight = Math.min( desiredHeight, |
342 |
document.documentElement.clientHeight - viewportSpacing.top - viewportSpacing.right ); |
343 |
allotedWidth = Math.ceil( desiredWidth - extraWidth ); |
344 |
allotedHeight = Math.ceil( desiredHeight - extraHeight ); |
345 |
naturalWidth = this.$clippable.prop( 'scrollWidth' ); |
346 |
naturalHeight = this.$clippable.prop( 'scrollHeight' ); |
347 |
clipWidth = allotedWidth < naturalWidth; |
348 |
clipHeight = allotedHeight < naturalHeight; |
349 |
|
350 |
if ( clipWidth ) { |
351 |
// The order matters here. If overflow is not set first, Chrome displays bogus scrollbars. See T157672. |
352 |
// Forcing a reflow is a smaller workaround than calling reconsiderScrollbars() for this case. |
353 |
this.$clippable.css( 'overflowX', 'scroll' ); |
354 |
void this.$clippable[ 0 ].offsetHeight; // Force reflow |
355 |
this.$clippable.css( { |
356 |
width: Math.max( 0, allotedWidth ), |
357 |
maxWidth: '' |
358 |
} ); |
359 |
} else { |
360 |
this.$clippable.css( { |
361 |
overflowX: '', |
362 |
width: this.idealWidth || '', |
363 |
maxWidth: Math.max( 0, allotedWidth ) |
364 |
} ); |
365 |
} |
366 |
if ( clipHeight ) { |
367 |
// The order matters here. If overflow is not set first, Chrome displays bogus scrollbars. See T157672. |
368 |
// Forcing a reflow is a smaller workaround than calling reconsiderScrollbars() for this case. |
369 |
this.$clippable.css( 'overflowY', 'scroll' ); |
370 |
void this.$clippable[ 0 ].offsetHeight; // Force reflow |
371 |
this.$clippable.css( { |
372 |
height: Math.max( 0, allotedHeight ), |
373 |
maxHeight: '' |
374 |
} ); |
375 |
} else { |
376 |
this.$clippable.css( { |
377 |
overflowY: '', |
378 |
height: this.idealHeight || '', |
379 |
maxHeight: Math.max( 0, allotedHeight ) |
380 |
} ); |
381 |
} |
382 |
|
383 |
// If we stopped clipping in at least one of the dimensions |
384 |
if ( ( this.clippedHorizontally && !clipWidth ) || ( this.clippedVertically && !clipHeight ) ) { |
385 |
OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] ); |
386 |
} |
387 |
|
388 |
this.clippedHorizontally = clipWidth; |
389 |
this.clippedVertically = clipHeight; |
390 |
|
391 |
return this; |
392 |
}; |
393 |
|
|
|
/src/mixins/DraggableElement.js
|
0 problems
|
|
/src/mixins/DraggableGroupElement.js
|
2 problems (2 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 190, Column 14: "Prefer WeakMap to $.data"
jquery/no-data
|
Error |
Row 190, Column 14: "Prefer closest to $.closest"
jquery/no-closest
|
Line |
Source |
1 |
/** |
2 |
* DraggableGroupElement is a mixin class used to create a group element to |
3 |
* contain draggable elements, which are items that can be clicked and dragged by a mouse. |
4 |
* The class is used with OO.ui.mixin.DraggableElement. |
5 |
* |
6 |
* @abstract |
7 |
* @class |
8 |
* @mixins OO.ui.mixin.GroupElement |
9 |
* |
10 |
* @constructor |
11 |
* @param {Object} [config] Configuration options |
12 |
* @cfg {string} [orientation] Item orientation: 'horizontal' or 'vertical'. The orientation |
13 |
* should match the layout of the items. Items displayed in a single row |
14 |
* or in several rows should use horizontal orientation. The vertical orientation should only be |
15 |
* used when the items are displayed in a single column. Defaults to 'vertical' |
16 |
* @cfg {boolean} [draggable] The items are draggable. This can change with #toggleDraggable |
17 |
*/ |
18 |
OO.ui.mixin.DraggableGroupElement = function OoUiMixinDraggableGroupElement( config ) { |
19 |
// Configuration initialization |
20 |
config = config || {}; |
21 |
|
22 |
// Parent constructor |
23 |
OO.ui.mixin.GroupElement.call( this, config ); |
24 |
|
25 |
// Properties |
26 |
this.orientation = config.orientation || 'vertical'; |
27 |
this.dragItem = null; |
28 |
this.itemKeys = {}; |
29 |
this.dir = null; |
30 |
this.itemsOrder = null; |
31 |
this.draggable = config.draggable === undefined ? true : !!config.draggable; |
32 |
|
33 |
// Events |
34 |
this.aggregate( { |
35 |
dragstart: 'itemDragStart', |
36 |
dragend: 'itemDragEnd', |
37 |
drop: 'itemDrop' |
38 |
} ); |
39 |
this.connect( this, { |
40 |
itemDragStart: 'onItemDragStart', |
41 |
itemDrop: 'onItemDropOrDragEnd', |
42 |
itemDragEnd: 'onItemDropOrDragEnd' |
43 |
} ); |
44 |
|
45 |
// Initialize |
46 |
if ( Array.isArray( config.items ) ) { |
47 |
this.addItems( config.items ); |
48 |
} |
49 |
this.$element |
50 |
.addClass( 'oo-ui-draggableGroupElement' ) |
51 |
.toggleClass( 'oo-ui-draggableGroupElement-horizontal', this.orientation === 'horizontal' ); |
52 |
}; |
53 |
|
54 |
/* Setup */ |
55 |
OO.mixinClass( OO.ui.mixin.DraggableGroupElement, OO.ui.mixin.GroupElement ); |
56 |
|
57 |
/* Events */ |
58 |
|
59 |
/** |
60 |
* An item has been dragged to a new position, but not yet dropped. |
61 |
* |
62 |
* @event drag |
63 |
* @param {OO.ui.mixin.DraggableElement} item Dragged item |
64 |
* @param {number} [newIndex] New index for the item |
65 |
*/ |
66 |
|
67 |
/** |
68 |
* An item has been dropped at a new position. |
69 |
* |
70 |
* @event reorder |
71 |
* @param {OO.ui.mixin.DraggableElement} item Reordered item |
72 |
* @param {number} [newIndex] New index for the item |
73 |
*/ |
74 |
|
75 |
/** |
76 |
* Draggable state of this widget has changed. |
77 |
* |
78 |
* @event draggable |
79 |
* @param {boolean} [draggable] Widget is draggable |
80 |
*/ |
81 |
|
82 |
/* Methods */ |
83 |
|
84 |
/** |
85 |
* Change the draggable state of this widget. |
86 |
* This allows users to temporarily halt the dragging operations. |
87 |
* |
88 |
* @param {boolean} isDraggable Widget supports draggable operations |
89 |
* @fires draggable |
90 |
*/ |
91 |
OO.ui.mixin.DraggableGroupElement.prototype.toggleDraggable = function ( isDraggable ) { |
92 |
isDraggable = isDraggable !== undefined ? !!isDraggable : !this.draggable; |
93 |
|
94 |
if ( this.draggable !== isDraggable ) { |
95 |
this.draggable = isDraggable; |
96 |
|
97 |
// Tell the items their draggable state changed |
98 |
this.getItems().forEach( function ( item ) { |
99 |
item.toggleDraggable( this.draggable ); |
100 |
}.bind( this ) ); |
101 |
|
102 |
// Emit event |
103 |
this.emit( 'draggable', this.draggable ); |
104 |
} |
105 |
}; |
106 |
|
107 |
/** |
108 |
* Check the draggable state of this widget |
109 |
* |
110 |
* @return {boolean} Widget supports draggable operations |
111 |
*/ |
112 |
OO.ui.mixin.DraggableGroupElement.prototype.isDraggable = function () { |
113 |
return this.draggable; |
114 |
}; |
115 |
|
116 |
/** |
117 |
* Respond to item drag start event |
118 |
* |
119 |
* @private |
120 |
* @param {OO.ui.mixin.DraggableElement} item Dragged item |
121 |
*/ |
122 |
OO.ui.mixin.DraggableGroupElement.prototype.onItemDragStart = function ( item ) { |
123 |
if ( !this.isDraggable() ) { |
124 |
return; |
125 |
} |
126 |
// Make a shallow copy of this.items so we can re-order it during previews |
127 |
// without affecting the original array. |
128 |
this.itemsOrder = this.items.slice(); |
129 |
this.updateIndexes(); |
130 |
if ( this.orientation === 'horizontal' ) { |
131 |
// Calculate and cache directionality on drag start - it's a little |
132 |
// expensive and it shouldn't change while dragging. |
133 |
this.dir = this.$element.css( 'direction' ); |
134 |
} |
135 |
this.setDragItem( item ); |
136 |
}; |
137 |
|
138 |
/** |
139 |
* Update the index properties of the items |
140 |
*/ |
141 |
OO.ui.mixin.DraggableGroupElement.prototype.updateIndexes = function () { |
142 |
var i, len; |
143 |
|
144 |
// Map the index of each object |
145 |
for ( i = 0, len = this.itemsOrder.length; i < len; i++ ) { |
146 |
this.itemsOrder[ i ].setIndex( i ); |
147 |
} |
148 |
}; |
149 |
|
150 |
/** |
151 |
* Handle drop or dragend event and switch the order of the items accordingly |
152 |
* |
153 |
* @private |
154 |
* @param {OO.ui.mixin.DraggableElement} item Dropped item |
155 |
*/ |
156 |
OO.ui.mixin.DraggableGroupElement.prototype.onItemDropOrDragEnd = function () { |
157 |
var targetIndex, originalIndex, |
158 |
item = this.getDragItem(); |
159 |
|
160 |
// TODO: Figure out a way to configure a list of legally droppable |
161 |
// elements even if they are not yet in the list |
162 |
if ( item ) { |
163 |
originalIndex = this.items.indexOf( item ); |
164 |
// If the item has moved forward, add one to the index to account for the left shift |
165 |
targetIndex = item.getIndex() + ( item.getIndex() > originalIndex ? 1 : 0 ); |
166 |
if ( targetIndex !== originalIndex ) { |
167 |
this.reorder( this.getDragItem(), targetIndex ); |
168 |
this.emit( 'reorder', this.getDragItem(), targetIndex ); |
169 |
} |
170 |
this.updateIndexes(); |
171 |
} |
172 |
this.unsetDragItem(); |
173 |
// Return false to prevent propogation |
174 |
return false; |
175 |
}; |
176 |
|
177 |
/** |
178 |
* Respond to dragover event |
179 |
* |
180 |
* @private |
181 |
* @param {jQuery.Event} e Dragover event |
182 |
* @fires reorder |
183 |
*/ |
184 |
OO.ui.mixin.DraggableGroupElement.prototype.onDragOver = function ( e ) { |
185 |
var overIndex, targetIndex, |
186 |
item = this.getDragItem(), |
187 |
dragItemIndex = item.getIndex(); |
188 |
|
189 |
// Get the OptionWidget item we are dragging over |
Error |
Row 190, Column 14: "Prefer WeakMap to $.data"
jquery/no-data
|
Error |
Row 190, Column 14: "Prefer closest to $.closest"
jquery/no-closest
|
190 |
overIndex = $( e.target ).closest( '.oo-ui-draggableElement' ).data( 'index' ); |
191 |
|
192 |
if ( overIndex !== undefined && overIndex !== dragItemIndex ) { |
193 |
targetIndex = overIndex + ( overIndex > dragItemIndex ? 1 : 0 ); |
194 |
|
195 |
if ( targetIndex > 0 ) { |
196 |
this.$group.children().eq( targetIndex - 1 ).after( item.$element ); |
197 |
} else { |
198 |
this.$group.prepend( item.$element ); |
199 |
} |
200 |
// Move item in itemsOrder array |
201 |
this.itemsOrder.splice( overIndex, 0, |
202 |
this.itemsOrder.splice( dragItemIndex, 1 )[ 0 ] |
203 |
); |
204 |
this.updateIndexes(); |
205 |
this.emit( 'drag', item, targetIndex ); |
206 |
} |
207 |
// Prevent default |
208 |
e.preventDefault(); |
209 |
}; |
210 |
|
211 |
/** |
212 |
* Reorder the items in the group |
213 |
* |
214 |
* @param {OO.ui.mixin.DraggableElement} item Reordered item |
215 |
* @param {number} newIndex New index |
216 |
*/ |
217 |
OO.ui.mixin.DraggableGroupElement.prototype.reorder = function ( item, newIndex ) { |
218 |
this.addItems( [ item ], newIndex ); |
219 |
}; |
220 |
|
221 |
/** |
222 |
* Set a dragged item |
223 |
* |
224 |
* @param {OO.ui.mixin.DraggableElement} item Dragged item |
225 |
*/ |
226 |
OO.ui.mixin.DraggableGroupElement.prototype.setDragItem = function ( item ) { |
227 |
if ( this.dragItem !== item ) { |
228 |
this.dragItem = item; |
229 |
this.$element.on( 'dragover', this.onDragOver.bind( this ) ); |
230 |
this.$element.addClass( 'oo-ui-draggableGroupElement-dragging' ); |
231 |
} |
232 |
}; |
233 |
|
234 |
/** |
235 |
* Unset the current dragged item |
236 |
*/ |
237 |
OO.ui.mixin.DraggableGroupElement.prototype.unsetDragItem = function () { |
238 |
if ( this.dragItem ) { |
239 |
this.dragItem = null; |
240 |
this.$element.off( 'dragover' ); |
241 |
this.$element.removeClass( 'oo-ui-draggableGroupElement-dragging' ); |
242 |
} |
243 |
}; |
244 |
|
245 |
/** |
246 |
* Get the item that is currently being dragged. |
247 |
* |
248 |
* @return {OO.ui.mixin.DraggableElement|null} The currently dragged item, or `null` if no item is being dragged |
249 |
*/ |
250 |
OO.ui.mixin.DraggableGroupElement.prototype.getDragItem = function () { |
251 |
return this.dragItem; |
252 |
}; |
253 |
|
|
|
/src/mixins/FlaggedElement.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 88, Column 18: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* The FlaggedElement class is an attribute mixin, meaning that it is used to add |
3 |
* additional functionality to an element created by another class. The class provides |
4 |
* a ‘flags’ property assigned the name (or an array of names) of styling flags, |
5 |
* which are used to customize the look and feel of a widget to better describe its |
6 |
* importance and functionality. |
7 |
* |
8 |
* The library currently contains the following styling flags for general use: |
9 |
* |
10 |
* - **progressive**: Progressive styling is applied to convey that the widget will move the user forward in a process. |
11 |
* - **destructive**: Destructive styling is applied to convey that the widget will remove something. |
12 |
* |
13 |
* The flags affect the appearance of the buttons: |
14 |
* |
15 |
* @example |
16 |
* // FlaggedElement is mixed into ButtonWidget to provide styling flags |
17 |
* var button1 = new OO.ui.ButtonWidget( { |
18 |
* label: 'Progressive', |
19 |
* flags: 'progressive' |
20 |
* } ); |
21 |
* var button2 = new OO.ui.ButtonWidget( { |
22 |
* label: 'Destructive', |
23 |
* flags: 'destructive' |
24 |
* } ); |
25 |
* $( 'body' ).append( button1.$element, button2.$element ); |
26 |
* |
27 |
* {@link OO.ui.ActionWidget ActionWidgets}, which are a special kind of button that execute an action, use these flags: **primary** and **safe**. |
28 |
* Please see the [OOUI documentation on MediaWiki] [1] for more information. |
29 |
* |
30 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Elements/Flagged |
31 |
* |
32 |
* @abstract |
33 |
* @class |
34 |
* |
35 |
* @constructor |
36 |
* @param {Object} [config] Configuration options |
37 |
* @cfg {string|string[]} [flags] The name or names of the flags (e.g., 'progressive' or 'primary') to apply. |
38 |
* Please see the [OOUI documentation on MediaWiki] [2] for more information about available flags. |
39 |
* [2]: https://www.mediawiki.org/wiki/OOUI/Elements/Flagged |
40 |
* @cfg {jQuery} [$flagged] The flagged element. By default, |
41 |
* the flagged functionality is applied to the element created by the class ($element). |
42 |
* If a different element is specified, the flagged functionality will be applied to it instead. |
43 |
*/ |
44 |
OO.ui.mixin.FlaggedElement = function OoUiMixinFlaggedElement( config ) { |
45 |
// Configuration initialization |
46 |
config = config || {}; |
47 |
|
48 |
// Properties |
49 |
this.flags = {}; |
50 |
this.$flagged = null; |
51 |
|
52 |
// Initialization |
53 |
this.setFlags( config.flags ); |
54 |
this.setFlaggedElement( config.$flagged || this.$element ); |
55 |
}; |
56 |
|
57 |
/* Events */ |
58 |
|
59 |
/** |
60 |
* @event flag |
61 |
* A flag event is emitted when the #clearFlags or #setFlags methods are used. The `changes` |
62 |
* parameter contains the name of each modified flag and indicates whether it was |
63 |
* added or removed. |
64 |
* |
65 |
* @param {Object.<string,boolean>} changes Object keyed by flag name. A Boolean `true` indicates |
66 |
* that the flag was added, `false` that the flag was removed. |
67 |
*/ |
68 |
|
69 |
/* Methods */ |
70 |
|
71 |
/** |
72 |
* Set the flagged element. |
73 |
* |
74 |
* This method is used to retarget a flagged mixin so that its functionality applies to the specified element. |
75 |
* If an element is already set, the method will remove the mixin’s effect on that element. |
76 |
* |
77 |
* @param {jQuery} $flagged Element that should be flagged |
78 |
*/ |
79 |
OO.ui.mixin.FlaggedElement.prototype.setFlaggedElement = function ( $flagged ) { |
80 |
var classNames = Object.keys( this.flags ).map( function ( flag ) { |
81 |
return 'oo-ui-flaggedElement-' + flag; |
82 |
} ); |
83 |
|
84 |
if ( this.$flagged ) { |
85 |
this.$flagged.removeClass( classNames ); |
86 |
} |
87 |
|
Error |
Row 88, Column 18: "Prefer classList to $.addClass"
jquery/no-class
|
88 |
this.$flagged = $flagged.addClass( classNames ); |
89 |
}; |
90 |
|
91 |
/** |
92 |
* Check if the specified flag is set. |
93 |
* |
94 |
* @param {string} flag Name of flag |
95 |
* @return {boolean} The flag is set |
96 |
*/ |
97 |
OO.ui.mixin.FlaggedElement.prototype.hasFlag = function ( flag ) { |
98 |
// This may be called before the constructor, thus before this.flags is set |
99 |
return this.flags && ( flag in this.flags ); |
100 |
}; |
101 |
|
102 |
/** |
103 |
* Get the names of all flags set. |
104 |
* |
105 |
* @return {string[]} Flag names |
106 |
*/ |
107 |
OO.ui.mixin.FlaggedElement.prototype.getFlags = function () { |
108 |
// This may be called before the constructor, thus before this.flags is set |
109 |
return Object.keys( this.flags || {} ); |
110 |
}; |
111 |
|
112 |
/** |
113 |
* Clear all flags. |
114 |
* |
115 |
* @chainable |
116 |
* @fires flag |
117 |
*/ |
118 |
OO.ui.mixin.FlaggedElement.prototype.clearFlags = function () { |
119 |
var flag, className, |
120 |
changes = {}, |
121 |
remove = [], |
122 |
classPrefix = 'oo-ui-flaggedElement-'; |
123 |
|
124 |
for ( flag in this.flags ) { |
125 |
className = classPrefix + flag; |
126 |
changes[ flag ] = false; |
127 |
delete this.flags[ flag ]; |
128 |
remove.push( className ); |
129 |
} |
130 |
|
131 |
if ( this.$flagged ) { |
132 |
this.$flagged.removeClass( remove ); |
133 |
} |
134 |
|
135 |
this.updateThemeClasses(); |
136 |
this.emit( 'flag', changes ); |
137 |
|
138 |
return this; |
139 |
}; |
140 |
|
141 |
/** |
142 |
* Add one or more flags. |
143 |
* |
144 |
* @param {string|string[]|Object.<string, boolean>} flags A flag name, an array of flag names, |
145 |
* or an object keyed by flag name with a boolean value that indicates whether the flag should |
146 |
* be added (`true`) or removed (`false`). |
147 |
* @chainable |
148 |
* @fires flag |
149 |
*/ |
150 |
OO.ui.mixin.FlaggedElement.prototype.setFlags = function ( flags ) { |
151 |
var i, len, flag, className, |
152 |
changes = {}, |
153 |
add = [], |
154 |
remove = [], |
155 |
classPrefix = 'oo-ui-flaggedElement-'; |
156 |
|
157 |
if ( typeof flags === 'string' ) { |
158 |
className = classPrefix + flags; |
159 |
// Set |
160 |
if ( !this.flags[ flags ] ) { |
161 |
this.flags[ flags ] = true; |
162 |
add.push( className ); |
163 |
} |
164 |
} else if ( Array.isArray( flags ) ) { |
165 |
for ( i = 0, len = flags.length; i < len; i++ ) { |
166 |
flag = flags[ i ]; |
167 |
className = classPrefix + flag; |
168 |
// Set |
169 |
if ( !this.flags[ flag ] ) { |
170 |
changes[ flag ] = true; |
171 |
this.flags[ flag ] = true; |
172 |
add.push( className ); |
173 |
} |
174 |
} |
175 |
} else if ( OO.isPlainObject( flags ) ) { |
176 |
for ( flag in flags ) { |
177 |
className = classPrefix + flag; |
178 |
if ( flags[ flag ] ) { |
179 |
// Set |
180 |
if ( !this.flags[ flag ] ) { |
181 |
changes[ flag ] = true; |
182 |
this.flags[ flag ] = true; |
183 |
add.push( className ); |
184 |
} |
185 |
} else { |
186 |
// Remove |
187 |
if ( this.flags[ flag ] ) { |
188 |
changes[ flag ] = false; |
189 |
delete this.flags[ flag ]; |
190 |
remove.push( className ); |
191 |
} |
192 |
} |
193 |
} |
194 |
} |
195 |
|
196 |
if ( this.$flagged ) { |
197 |
this.$flagged |
198 |
.addClass( add ) |
199 |
.removeClass( remove ); |
200 |
} |
201 |
|
202 |
this.updateThemeClasses(); |
203 |
this.emit( 'flag', changes ); |
204 |
|
205 |
return this; |
206 |
}; |
207 |
|
|
|
/src/mixins/FloatableElement.js
|
11 problems (11 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 71, Column 20: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 156, Column 8: "Prefer matches to $.is"
jquery/no-is
|
Error |
Row 198, Column 15: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 323, Column 7: "Prefer matches to $.is"
jquery/no-is
|
Error |
Row 328, Column 11: "Prefer matches to $.is"
jquery/no-is
|
Error |
Row 329, Column 16: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 329, Column 66: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 330, Column 16: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 330, Column 66: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 332, Column 52: "Prefer direct property access to $.prop"
jquery/no-prop
|
Error |
Row 333, Column 55: "Prefer direct property access to $.prop"
jquery/no-prop
|
Line |
Source |
1 |
/** |
2 |
* Element that will stick adjacent to a specified container, even when it is inserted elsewhere |
3 |
* in the document (for example, in an OO.ui.Window's $overlay). |
4 |
* |
5 |
* The elements's position is automatically calculated and maintained when window is resized or the |
6 |
* page is scrolled. If you reposition the container manually, you have to call #position to make |
7 |
* sure the element is still placed correctly. |
8 |
* |
9 |
* As positioning is only possible when both the element and the container are attached to the DOM |
10 |
* and visible, it's only done after you call #togglePositioning. You might want to do this inside |
11 |
* the #toggle method to display a floating popup, for example. |
12 |
* |
13 |
* @abstract |
14 |
* @class |
15 |
* |
16 |
* @constructor |
17 |
* @param {Object} [config] Configuration options |
18 |
* @cfg {jQuery} [$floatable] Node to position, assigned to #$floatable, omit to use #$element |
19 |
* @cfg {jQuery} [$floatableContainer] Node to position adjacent to |
20 |
* @cfg {string} [verticalPosition='below'] Where to position $floatable vertically: |
21 |
* 'below': Directly below $floatableContainer, aligning f's top edge with fC's bottom edge |
22 |
* 'above': Directly above $floatableContainer, aligning f's bottom edge with fC's top edge |
23 |
* 'top': Align the top edge with $floatableContainer's top edge |
24 |
* 'bottom': Align the bottom edge with $floatableContainer's bottom edge |
25 |
* 'center': Vertically align the center with $floatableContainer's center |
26 |
* @cfg {string} [horizontalPosition='start'] Where to position $floatable horizontally: |
27 |
* 'before': Directly before $floatableContainer, aligning f's end edge with fC's start edge |
28 |
* 'after': Directly after $floatableContainer, aligning f's start edge with fC's end edge |
29 |
* 'start': Align the start (left in LTR, right in RTL) edge with $floatableContainer's start edge |
30 |
* 'end': Align the end (right in LTR, left in RTL) edge with $floatableContainer's end edge |
31 |
* 'center': Horizontally align the center with $floatableContainer's center |
32 |
* @cfg {boolean} [hideWhenOutOfView=true] Whether to hide the floatable element if the container |
33 |
* is out of view |
34 |
*/ |
35 |
OO.ui.mixin.FloatableElement = function OoUiMixinFloatableElement( config ) { |
36 |
// Configuration initialization |
37 |
config = config || {}; |
38 |
|
39 |
// Properties |
40 |
this.$floatable = null; |
41 |
this.$floatableContainer = null; |
42 |
this.$floatableWindow = null; |
43 |
this.$floatableClosestScrollable = null; |
44 |
this.floatableOutOfView = false; |
45 |
this.onFloatableScrollHandler = this.position.bind( this ); |
46 |
this.onFloatableWindowResizeHandler = this.position.bind( this ); |
47 |
|
48 |
// Initialization |
49 |
this.setFloatableContainer( config.$floatableContainer ); |
50 |
this.setFloatableElement( config.$floatable || this.$element ); |
51 |
this.setVerticalPosition( config.verticalPosition || 'below' ); |
52 |
this.setHorizontalPosition( config.horizontalPosition || 'start' ); |
53 |
this.hideWhenOutOfView = config.hideWhenOutOfView === undefined ? true : !!config.hideWhenOutOfView; |
54 |
}; |
55 |
|
56 |
/* Methods */ |
57 |
|
58 |
/** |
59 |
* Set floatable element. |
60 |
* |
61 |
* If an element is already set, it will be cleaned up before setting up the new element. |
62 |
* |
63 |
* @param {jQuery} $floatable Element to make floatable |
64 |
*/ |
65 |
OO.ui.mixin.FloatableElement.prototype.setFloatableElement = function ( $floatable ) { |
66 |
if ( this.$floatable ) { |
67 |
this.$floatable.removeClass( 'oo-ui-floatableElement-floatable' ); |
68 |
this.$floatable.css( { left: '', top: '' } ); |
69 |
} |
70 |
|
Error |
Row 71, Column 20: "Prefer classList to $.addClass"
jquery/no-class
|
71 |
this.$floatable = $floatable.addClass( 'oo-ui-floatableElement-floatable' ); |
72 |
this.position(); |
73 |
}; |
74 |
|
75 |
/** |
76 |
* Set floatable container. |
77 |
* |
78 |
* The element will be positioned relative to the specified container. |
79 |
* |
80 |
* @param {jQuery|null} $floatableContainer Container to keep visible, or null to unset |
81 |
*/ |
82 |
OO.ui.mixin.FloatableElement.prototype.setFloatableContainer = function ( $floatableContainer ) { |
83 |
this.$floatableContainer = $floatableContainer; |
84 |
if ( this.$floatable ) { |
85 |
this.position(); |
86 |
} |
87 |
}; |
88 |
|
89 |
/** |
90 |
* Change how the element is positioned vertically. |
91 |
* |
92 |
* @param {string} position 'below', 'above', 'top', 'bottom' or 'center' |
93 |
*/ |
94 |
OO.ui.mixin.FloatableElement.prototype.setVerticalPosition = function ( position ) { |
95 |
if ( [ 'below', 'above', 'top', 'bottom', 'center' ].indexOf( position ) === -1 ) { |
96 |
throw new Error( 'Invalid value for vertical position: ' + position ); |
97 |
} |
98 |
if ( this.verticalPosition !== position ) { |
99 |
this.verticalPosition = position; |
100 |
if ( this.$floatable ) { |
101 |
this.position(); |
102 |
} |
103 |
} |
104 |
}; |
105 |
|
106 |
/** |
107 |
* Change how the element is positioned horizontally. |
108 |
* |
109 |
* @param {string} position 'before', 'after', 'start', 'end' or 'center' |
110 |
*/ |
111 |
OO.ui.mixin.FloatableElement.prototype.setHorizontalPosition = function ( position ) { |
112 |
if ( [ 'before', 'after', 'start', 'end', 'center' ].indexOf( position ) === -1 ) { |
113 |
throw new Error( 'Invalid value for horizontal position: ' + position ); |
114 |
} |
115 |
if ( this.horizontalPosition !== position ) { |
116 |
this.horizontalPosition = position; |
117 |
if ( this.$floatable ) { |
118 |
this.position(); |
119 |
} |
120 |
} |
121 |
}; |
122 |
|
123 |
/** |
124 |
* Toggle positioning. |
125 |
* |
126 |
* Do not turn positioning on until after the element is attached to the DOM and visible. |
127 |
* |
128 |
* @param {boolean} [positioning] Enable positioning, omit to toggle |
129 |
* @chainable |
130 |
*/ |
131 |
OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( positioning ) { |
132 |
var closestScrollableOfContainer; |
133 |
|
134 |
if ( !this.$floatable || !this.$floatableContainer ) { |
135 |
return this; |
136 |
} |
137 |
|
138 |
positioning = positioning === undefined ? !this.positioning : !!positioning; |
139 |
|
140 |
if ( positioning && !this.warnedUnattached && !this.isElementAttached() ) { |
141 |
OO.ui.warnDeprecation( 'FloatableElement#togglePositioning: Before calling this method, the element must be attached to the DOM.' ); |
142 |
this.warnedUnattached = true; |
143 |
} |
144 |
|
145 |
if ( this.positioning !== positioning ) { |
146 |
this.positioning = positioning; |
147 |
|
148 |
this.needsCustomPosition = |
149 |
this.verticalPosition !== 'below' || |
150 |
this.horizontalPosition !== 'start' || |
151 |
!OO.ui.contains( this.$floatableContainer[ 0 ], this.$floatable[ 0 ] ); |
152 |
|
153 |
closestScrollableOfContainer = OO.ui.Element.static.getClosestScrollableContainer( this.$floatableContainer[ 0 ] ); |
154 |
// If the scrollable is the root, we have to listen to scroll events |
155 |
// on the window because of browser inconsistencies. |
Error |
Row 156, Column 8: "Prefer matches to $.is"
jquery/no-is
|
156 |
if ( $( closestScrollableOfContainer ).is( 'html, body' ) ) { |
157 |
closestScrollableOfContainer = OO.ui.Element.static.getWindow( closestScrollableOfContainer ); |
158 |
} |
159 |
|
160 |
if ( positioning ) { |
161 |
this.$floatableWindow = $( this.getElementWindow() ); |
162 |
this.$floatableWindow.on( 'resize', this.onFloatableWindowResizeHandler ); |
163 |
|
164 |
this.$floatableClosestScrollable = $( closestScrollableOfContainer ); |
165 |
this.$floatableClosestScrollable.on( 'scroll', this.onFloatableScrollHandler ); |
166 |
|
167 |
// Initial position after visible |
168 |
this.position(); |
169 |
} else { |
170 |
if ( this.$floatableWindow ) { |
171 |
this.$floatableWindow.off( 'resize', this.onFloatableWindowResizeHandler ); |
172 |
this.$floatableWindow = null; |
173 |
} |
174 |
|
175 |
if ( this.$floatableClosestScrollable ) { |
176 |
this.$floatableClosestScrollable.off( 'scroll', this.onFloatableScrollHandler ); |
177 |
this.$floatableClosestScrollable = null; |
178 |
} |
179 |
|
180 |
this.$floatable.css( { left: '', right: '', top: '' } ); |
181 |
} |
182 |
} |
183 |
|
184 |
return this; |
185 |
}; |
186 |
|
187 |
/** |
188 |
* Check whether the bottom edge of the given element is within the viewport of the given container. |
189 |
* |
190 |
* @private |
191 |
* @param {jQuery} $element |
192 |
* @param {jQuery} $container |
193 |
* @return {boolean} |
194 |
*/ |
195 |
OO.ui.mixin.FloatableElement.prototype.isElementInViewport = function ( $element, $container ) { |
196 |
var elemRect, contRect, topEdgeInBounds, bottomEdgeInBounds, leftEdgeInBounds, rightEdgeInBounds, |
197 |
startEdgeInBounds, endEdgeInBounds, viewportSpacing, |
Error |
Row 198, Column 15: "Prefer getComputedStyle to $.css"
jquery/no-css
|
198 |
direction = $element.css( 'direction' ); |
199 |
|
200 |
elemRect = $element[ 0 ].getBoundingClientRect(); |
201 |
if ( $container[ 0 ] === window ) { |
202 |
viewportSpacing = OO.ui.getViewportSpacing(); |
203 |
contRect = { |
204 |
top: 0, |
205 |
left: 0, |
206 |
right: document.documentElement.clientWidth, |
207 |
bottom: document.documentElement.clientHeight |
208 |
}; |
209 |
contRect.top += viewportSpacing.top; |
210 |
contRect.left += viewportSpacing.left; |
211 |
contRect.right -= viewportSpacing.right; |
212 |
contRect.bottom -= viewportSpacing.bottom; |
213 |
} else { |
214 |
contRect = $container[ 0 ].getBoundingClientRect(); |
215 |
} |
216 |
|
217 |
topEdgeInBounds = elemRect.top >= contRect.top && elemRect.top <= contRect.bottom; |
218 |
bottomEdgeInBounds = elemRect.bottom >= contRect.top && elemRect.bottom <= contRect.bottom; |
219 |
leftEdgeInBounds = elemRect.left >= contRect.left && elemRect.left <= contRect.right; |
220 |
rightEdgeInBounds = elemRect.right >= contRect.left && elemRect.right <= contRect.right; |
221 |
if ( direction === 'rtl' ) { |
222 |
startEdgeInBounds = rightEdgeInBounds; |
223 |
endEdgeInBounds = leftEdgeInBounds; |
224 |
} else { |
225 |
startEdgeInBounds = leftEdgeInBounds; |
226 |
endEdgeInBounds = rightEdgeInBounds; |
227 |
} |
228 |
|
229 |
if ( this.verticalPosition === 'below' && !bottomEdgeInBounds ) { |
230 |
return false; |
231 |
} |
232 |
if ( this.verticalPosition === 'above' && !topEdgeInBounds ) { |
233 |
return false; |
234 |
} |
235 |
if ( this.horizontalPosition === 'before' && !startEdgeInBounds ) { |
236 |
return false; |
237 |
} |
238 |
if ( this.horizontalPosition === 'after' && !endEdgeInBounds ) { |
239 |
return false; |
240 |
} |
241 |
|
242 |
// The other positioning values are all about being inside the container, |
243 |
// so in those cases all we care about is that any part of the container is visible. |
244 |
return elemRect.top <= contRect.bottom && elemRect.bottom >= contRect.top && |
245 |
elemRect.left <= contRect.right && elemRect.right >= contRect.left; |
246 |
}; |
247 |
|
248 |
/** |
249 |
* Check if the floatable is hidden to the user because it was offscreen. |
250 |
* |
251 |
* @return {boolean} Floatable is out of view |
252 |
*/ |
253 |
OO.ui.mixin.FloatableElement.prototype.isFloatableOutOfView = function () { |
254 |
return this.floatableOutOfView; |
255 |
}; |
256 |
|
257 |
/** |
258 |
* Position the floatable below its container. |
259 |
* |
260 |
* This should only be done when both of them are attached to the DOM and visible. |
261 |
* |
262 |
* @chainable |
263 |
*/ |
264 |
OO.ui.mixin.FloatableElement.prototype.position = function () { |
265 |
if ( !this.positioning ) { |
266 |
return this; |
267 |
} |
268 |
|
269 |
if ( !( |
270 |
// To continue, some things need to be true: |
271 |
// The element must actually be in the DOM |
272 |
this.isElementAttached() && ( |
273 |
// The closest scrollable is the current window |
274 |
this.$floatableClosestScrollable[ 0 ] === this.getElementWindow() || |
275 |
// OR is an element in the element's DOM |
276 |
$.contains( this.getElementDocument(), this.$floatableClosestScrollable[ 0 ] ) |
277 |
) |
278 |
) ) { |
279 |
// Abort early if important parts of the widget are no longer attached to the DOM |
280 |
return this; |
281 |
} |
282 |
|
283 |
this.floatableOutOfView = this.hideWhenOutOfView && !this.isElementInViewport( this.$floatableContainer, this.$floatableClosestScrollable ); |
284 |
if ( this.floatableOutOfView ) { |
285 |
this.$floatable.addClass( 'oo-ui-element-hidden' ); |
286 |
return this; |
287 |
} else { |
288 |
this.$floatable.removeClass( 'oo-ui-element-hidden' ); |
289 |
} |
290 |
|
291 |
if ( !this.needsCustomPosition ) { |
292 |
return this; |
293 |
} |
294 |
|
295 |
this.$floatable.css( this.computePosition() ); |
296 |
|
297 |
// We updated the position, so re-evaluate the clipping state. |
298 |
// (ClippableElement does not listen to 'scroll' events on $floatableContainer's parent, and so |
299 |
// will not notice the need to update itself.) |
300 |
// TODO: This is terrible, we shouldn't need to know about ClippableElement at all here. Why does |
301 |
// it not listen to the right events in the right places? |
302 |
if ( this.clip ) { |
303 |
this.clip(); |
304 |
} |
305 |
|
306 |
return this; |
307 |
}; |
308 |
|
309 |
/** |
310 |
* Compute how #$floatable should be positioned based on the position of #$floatableContainer |
311 |
* and the positioning settings. This is a helper for #position that shouldn't be called directly, |
312 |
* but may be overridden by subclasses if they want to change or add to the positioning logic. |
313 |
* |
314 |
* @return {Object} New position to apply with .css(). Keys are 'top', 'left', 'bottom' and 'right'. |
315 |
*/ |
316 |
OO.ui.mixin.FloatableElement.prototype.computePosition = function () { |
317 |
var isBody, scrollableX, scrollableY, containerPos, |
318 |
horizScrollbarHeight, vertScrollbarWidth, scrollTop, scrollLeft, |
319 |
newPos = { top: '', left: '', bottom: '', right: '' }, |
320 |
direction = this.$floatableContainer.css( 'direction' ), |
321 |
$offsetParent = this.$floatable.offsetParent(); |
322 |
|
Error |
Row 323, Column 7: "Prefer matches to $.is"
jquery/no-is
|
323 |
if ( $offsetParent.is( 'html' ) ) { |
324 |
// The innerHeight/Width and clientHeight/Width calculations don't work well on the |
325 |
// <html> element, but they do work on the <body> |
326 |
$offsetParent = $( $offsetParent[ 0 ].ownerDocument.body ); |
327 |
} |
Error |
Row 328, Column 11: "Prefer matches to $.is"
jquery/no-is
|
328 |
isBody = $offsetParent.is( 'body' ); |
Error |
Row 329, Column 16: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 329, Column 66: "Prefer getComputedStyle to $.css"
jquery/no-css
|
329 |
scrollableX = $offsetParent.css( 'overflow-x' ) === 'scroll' || $offsetParent.css( 'overflow-x' ) === 'auto'; |
Error |
Row 330, Column 16: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 330, Column 66: "Prefer getComputedStyle to $.css"
jquery/no-css
|
330 |
scrollableY = $offsetParent.css( 'overflow-y' ) === 'scroll' || $offsetParent.css( 'overflow-y' ) === 'auto'; |
331 |
|
Error |
Row 332, Column 52: "Prefer direct property access to $.prop"
jquery/no-prop
|
332 |
vertScrollbarWidth = $offsetParent.innerWidth() - $offsetParent.prop( 'clientWidth' ); |
Error |
Row 333, Column 55: "Prefer direct property access to $.prop"
jquery/no-prop
|
333 |
horizScrollbarHeight = $offsetParent.innerHeight() - $offsetParent.prop( 'clientHeight' ); |
334 |
// We don't need to compute and add scrollTop and scrollLeft if the scrollable container is the body, |
335 |
// or if it isn't scrollable |
336 |
scrollTop = scrollableY && !isBody ? $offsetParent.scrollTop() : 0; |
337 |
scrollLeft = scrollableX && !isBody ? OO.ui.Element.static.getScrollLeft( $offsetParent[ 0 ] ) : 0; |
338 |
|
339 |
// Avoid passing the <body> to getRelativePosition(), because it won't return what we expect |
340 |
// if the <body> has a margin |
341 |
containerPos = isBody ? |
342 |
this.$floatableContainer.offset() : |
343 |
OO.ui.Element.static.getRelativePosition( this.$floatableContainer, $offsetParent ); |
344 |
containerPos.bottom = containerPos.top + this.$floatableContainer.outerHeight(); |
345 |
containerPos.right = containerPos.left + this.$floatableContainer.outerWidth(); |
346 |
containerPos.start = direction === 'rtl' ? containerPos.right : containerPos.left; |
347 |
containerPos.end = direction === 'rtl' ? containerPos.left : containerPos.right; |
348 |
|
349 |
if ( this.verticalPosition === 'below' ) { |
350 |
newPos.top = containerPos.bottom; |
351 |
} else if ( this.verticalPosition === 'above' ) { |
352 |
newPos.bottom = $offsetParent.outerHeight() - containerPos.top; |
353 |
} else if ( this.verticalPosition === 'top' ) { |
354 |
newPos.top = containerPos.top; |
355 |
} else if ( this.verticalPosition === 'bottom' ) { |
356 |
newPos.bottom = $offsetParent.outerHeight() - containerPos.bottom; |
357 |
} else if ( this.verticalPosition === 'center' ) { |
358 |
newPos.top = containerPos.top + |
359 |
( this.$floatableContainer.height() - this.$floatable.height() ) / 2; |
360 |
} |
361 |
|
362 |
if ( this.horizontalPosition === 'before' ) { |
363 |
newPos.end = containerPos.start; |
364 |
} else if ( this.horizontalPosition === 'after' ) { |
365 |
newPos.start = containerPos.end; |
366 |
} else if ( this.horizontalPosition === 'start' ) { |
367 |
newPos.start = containerPos.start; |
368 |
} else if ( this.horizontalPosition === 'end' ) { |
369 |
newPos.end = containerPos.end; |
370 |
} else if ( this.horizontalPosition === 'center' ) { |
371 |
newPos.left = containerPos.left + |
372 |
( this.$floatableContainer.width() - this.$floatable.width() ) / 2; |
373 |
} |
374 |
|
375 |
if ( newPos.start !== undefined ) { |
376 |
if ( direction === 'rtl' ) { |
377 |
newPos.right = ( isBody ? $( $offsetParent[ 0 ].ownerDocument.documentElement ) : $offsetParent ).outerWidth() - newPos.start; |
378 |
} else { |
379 |
newPos.left = newPos.start; |
380 |
} |
381 |
delete newPos.start; |
382 |
} |
383 |
if ( newPos.end !== undefined ) { |
384 |
if ( direction === 'rtl' ) { |
385 |
newPos.left = newPos.end; |
386 |
} else { |
387 |
newPos.right = ( isBody ? $( $offsetParent[ 0 ].ownerDocument.documentElement ) : $offsetParent ).outerWidth() - newPos.end; |
388 |
} |
389 |
delete newPos.end; |
390 |
} |
391 |
|
392 |
// Account for scroll position |
393 |
if ( newPos.top !== '' ) { |
394 |
newPos.top += scrollTop; |
395 |
} |
396 |
if ( newPos.bottom !== '' ) { |
397 |
newPos.bottom -= scrollTop; |
398 |
} |
399 |
if ( newPos.left !== '' ) { |
400 |
newPos.left += scrollLeft; |
401 |
} |
402 |
if ( newPos.right !== '' ) { |
403 |
newPos.right -= scrollLeft; |
404 |
} |
405 |
|
406 |
// Account for scrollbar gutter |
407 |
if ( newPos.bottom !== '' ) { |
408 |
newPos.bottom -= horizScrollbarHeight; |
409 |
} |
410 |
if ( direction === 'rtl' ) { |
411 |
if ( newPos.left !== '' ) { |
412 |
newPos.left -= vertScrollbarWidth; |
413 |
} |
414 |
} else { |
415 |
if ( newPos.right !== '' ) { |
416 |
newPos.right -= vertScrollbarWidth; |
417 |
} |
418 |
} |
419 |
|
420 |
return newPos; |
421 |
}; |
422 |
|
|
|
/src/mixins/GroupElement.js
|
0 problems
|
|
/src/mixins/GroupWidget.js
|
0 problems
|
|
/src/mixins/IconElement.js
|
3 problems (3 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 106, Column 15: "Prefer classList to $.toggleClass"
jquery/no-class
|
Error |
Row 106, Column 15: "Prefer classList to $.toggleClass"
jquery/no-class
|
Error |
Row 106, Column 15: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* IconElement is often mixed into other classes to generate an icon. |
3 |
* Icons are graphics, about the size of normal text. They are used to aid the user |
4 |
* in locating a control or to convey information in a space-efficient way. See the |
5 |
* [OOUI documentation on MediaWiki] [1] for a list of icons |
6 |
* included in the library. |
7 |
* |
8 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Icons,_Indicators,_and_Labels#Icons |
9 |
* |
10 |
* @abstract |
11 |
* @class |
12 |
* |
13 |
* @constructor |
14 |
* @param {Object} [config] Configuration options |
15 |
* @cfg {jQuery} [$icon] The icon element created by the class. If this configuration is omitted, |
16 |
* the icon element will use a generated `<span>`. To use a different HTML tag, or to specify that |
17 |
* the icon element be set to an existing icon instead of the one generated by this class, set a |
18 |
* value using a jQuery selection. For example: |
19 |
* |
20 |
* // Use a <div> tag instead of a <span> |
21 |
* $icon: $("<div>") |
22 |
* // Use an existing icon element instead of the one generated by the class |
23 |
* $icon: this.$element |
24 |
* // Use an icon element from a child widget |
25 |
* $icon: this.childwidget.$element |
26 |
* @cfg {Object|string} [icon=''] The symbolic name of the icon (e.g., ‘remove’ or ‘menu’), or a map of |
27 |
* symbolic names. A map is used for i18n purposes and contains a `default` icon |
28 |
* name and additional names keyed by language code. The `default` name is used when no icon is keyed |
29 |
* by the user's language. |
30 |
* |
31 |
* Example of an i18n map: |
32 |
* |
33 |
* { default: 'bold-a', en: 'bold-b', de: 'bold-f' } |
34 |
* See the [OOUI documentation on MediaWiki] [2] for a list of icons included in the library. |
35 |
* [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Icons,_Indicators,_and_Labels#Icons |
36 |
* @cfg {string|Function} [iconTitle] A text string used as the icon title, or a function that returns title |
37 |
* text. The icon title is displayed when users move the mouse over the icon. |
38 |
*/ |
39 |
OO.ui.mixin.IconElement = function OoUiMixinIconElement( config ) { |
40 |
// Configuration initialization |
41 |
config = config || {}; |
42 |
|
43 |
// Properties |
44 |
this.$icon = null; |
45 |
this.icon = null; |
46 |
this.iconTitle = null; |
47 |
|
48 |
// Initialization |
49 |
this.setIcon( config.icon || this.constructor.static.icon ); |
50 |
this.setIconTitle( config.iconTitle || this.constructor.static.iconTitle ); |
51 |
this.setIconElement( config.$icon || $( '<span>' ) ); |
52 |
}; |
53 |
|
54 |
/* Setup */ |
55 |
|
56 |
OO.initClass( OO.ui.mixin.IconElement ); |
57 |
|
58 |
/* Static Properties */ |
59 |
|
60 |
/** |
61 |
* The symbolic name of the icon (e.g., ‘remove’ or ‘menu’), or a map of symbolic names. A map is used |
62 |
* for i18n purposes and contains a `default` icon name and additional names keyed by |
63 |
* language code. The `default` name is used when no icon is keyed by the user's language. |
64 |
* |
65 |
* Example of an i18n map: |
66 |
* |
67 |
* { default: 'bold-a', en: 'bold-b', de: 'bold-f' } |
68 |
* |
69 |
* Note: the static property will be overridden if the #icon configuration is used. |
70 |
* |
71 |
* @static |
72 |
* @inheritable |
73 |
* @property {Object|string} |
74 |
*/ |
75 |
OO.ui.mixin.IconElement.static.icon = null; |
76 |
|
77 |
/** |
78 |
* The icon title, displayed when users move the mouse over the icon. The value can be text, a |
79 |
* function that returns title text, or `null` for no title. |
80 |
* |
81 |
* The static property will be overridden if the #iconTitle configuration is used. |
82 |
* |
83 |
* @static |
84 |
* @inheritable |
85 |
* @property {string|Function|null} |
86 |
*/ |
87 |
OO.ui.mixin.IconElement.static.iconTitle = null; |
88 |
|
89 |
/* Methods */ |
90 |
|
91 |
/** |
92 |
* Set the icon element. This method is used to retarget an icon mixin so that its functionality |
93 |
* applies to the specified icon element instead of the one created by the class. If an icon |
94 |
* element is already set, the mixin’s effect on that element is removed. Generated CSS classes |
95 |
* and mixin methods will no longer affect the element. |
96 |
* |
97 |
* @param {jQuery} $icon Element to use as icon |
98 |
*/ |
99 |
OO.ui.mixin.IconElement.prototype.setIconElement = function ( $icon ) { |
100 |
if ( this.$icon ) { |
101 |
this.$icon |
102 |
.removeClass( 'oo-ui-iconElement-icon oo-ui-icon-' + this.icon ) |
103 |
.removeAttr( 'title' ); |
104 |
} |
105 |
|
Error |
Row 106, Column 15: "Prefer classList to $.toggleClass"
jquery/no-class
|
Error |
Row 106, Column 15: "Prefer classList to $.toggleClass"
jquery/no-class
|
Error |
Row 106, Column 15: "Prefer classList to $.addClass"
jquery/no-class
|
106 |
this.$icon = $icon |
107 |
.addClass( 'oo-ui-iconElement-icon' ) |
108 |
.toggleClass( 'oo-ui-iconElement-noIcon', !this.icon ) |
109 |
.toggleClass( 'oo-ui-icon-' + this.icon, !!this.icon ); |
110 |
if ( this.iconTitle !== null ) { |
111 |
this.$icon.attr( 'title', this.iconTitle ); |
112 |
} |
113 |
|
114 |
this.updateThemeClasses(); |
115 |
}; |
116 |
|
117 |
/** |
118 |
* Set icon by symbolic name (e.g., ‘remove’ or ‘menu’). Use `null` to remove an icon. |
119 |
* The icon parameter can also be set to a map of icon names. See the #icon config setting |
120 |
* for an example. |
121 |
* |
122 |
* @param {Object|string|null} icon A symbolic icon name, a {@link #icon map of icon names} keyed |
123 |
* by language code, or `null` to remove the icon. |
124 |
* @chainable |
125 |
*/ |
126 |
OO.ui.mixin.IconElement.prototype.setIcon = function ( icon ) { |
127 |
icon = OO.isPlainObject( icon ) ? OO.ui.getLocalValue( icon, null, 'default' ) : icon; |
128 |
icon = typeof icon === 'string' && icon.trim().length ? icon.trim() : null; |
129 |
|
130 |
if ( this.icon !== icon ) { |
131 |
if ( this.$icon ) { |
132 |
if ( this.icon !== null ) { |
133 |
this.$icon.removeClass( 'oo-ui-icon-' + this.icon ); |
134 |
} |
135 |
if ( icon !== null ) { |
136 |
this.$icon.addClass( 'oo-ui-icon-' + icon ); |
137 |
} |
138 |
} |
139 |
this.icon = icon; |
140 |
} |
141 |
|
142 |
this.$element.toggleClass( 'oo-ui-iconElement', !!this.icon ); |
143 |
if ( this.$icon ) { |
144 |
this.$icon.toggleClass( 'oo-ui-iconElement-noIcon', !this.icon ); |
145 |
} |
146 |
this.updateThemeClasses(); |
147 |
|
148 |
return this; |
149 |
}; |
150 |
|
151 |
/** |
152 |
* Set the icon title. Use `null` to remove the title. |
153 |
* |
154 |
* @param {string|Function|null} iconTitle A text string used as the icon title, |
155 |
* a function that returns title text, or `null` for no title. |
156 |
* @chainable |
157 |
*/ |
158 |
OO.ui.mixin.IconElement.prototype.setIconTitle = function ( iconTitle ) { |
159 |
iconTitle = |
160 |
( typeof iconTitle === 'function' || ( typeof iconTitle === 'string' && iconTitle.length ) ) ? |
161 |
OO.ui.resolveMsg( iconTitle ) : null; |
162 |
|
163 |
if ( this.iconTitle !== iconTitle ) { |
164 |
this.iconTitle = iconTitle; |
165 |
if ( this.$icon ) { |
166 |
if ( this.iconTitle !== null ) { |
167 |
this.$icon.attr( 'title', iconTitle ); |
168 |
} else { |
169 |
this.$icon.removeAttr( 'title' ); |
170 |
} |
171 |
} |
172 |
} |
173 |
|
174 |
return this; |
175 |
}; |
176 |
|
177 |
/** |
178 |
* Get the symbolic name of the icon. |
179 |
* |
180 |
* @return {string} Icon name |
181 |
*/ |
182 |
OO.ui.mixin.IconElement.prototype.getIcon = function () { |
183 |
return this.icon; |
184 |
}; |
185 |
|
186 |
/** |
187 |
* Get the icon title. The title text is displayed when a user moves the mouse over the icon. |
188 |
* |
189 |
* @return {string} Icon title text |
190 |
*/ |
191 |
OO.ui.mixin.IconElement.prototype.getIconTitle = function () { |
192 |
return this.iconTitle; |
193 |
}; |
194 |
|
|
|
/src/mixins/IndicatorElement.js
|
3 problems (3 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 87, Column 20: "Prefer classList to $.toggleClass"
jquery/no-class
|
Error |
Row 87, Column 20: "Prefer classList to $.toggleClass"
jquery/no-class
|
Error |
Row 87, Column 20: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* IndicatorElement is often mixed into other classes to generate an indicator. |
3 |
* Indicators are small graphics that are generally used in two ways: |
4 |
* |
5 |
* - To draw attention to the status of an item. For example, an indicator might be |
6 |
* used to show that an item in a list has errors that need to be resolved. |
7 |
* - To clarify the function of a control that acts in an exceptional way (a button |
8 |
* that opens a menu instead of performing an action directly, for example). |
9 |
* |
10 |
* For a list of indicators included in the library, please see the |
11 |
* [OOUI documentation on MediaWiki] [1]. |
12 |
* |
13 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Icons,_Indicators,_and_Labels#Indicators |
14 |
* |
15 |
* @abstract |
16 |
* @class |
17 |
* |
18 |
* @constructor |
19 |
* @param {Object} [config] Configuration options |
20 |
* @cfg {jQuery} [$indicator] The indicator element created by the class. If this |
21 |
* configuration is omitted, the indicator element will use a generated `<span>`. |
22 |
* @cfg {string} [indicator] Symbolic name of the indicator (e.g., ‘clear’ or ‘down’). |
23 |
* See the [OOUI documentation on MediaWiki][2] for a list of indicators included |
24 |
* in the library. |
25 |
* [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Icons,_Indicators,_and_Labels#Indicators |
26 |
* @cfg {string|Function} [indicatorTitle] A text string used as the indicator title, |
27 |
* or a function that returns title text. The indicator title is displayed when users move |
28 |
* the mouse over the indicator. |
29 |
*/ |
30 |
OO.ui.mixin.IndicatorElement = function OoUiMixinIndicatorElement( config ) { |
31 |
// Configuration initialization |
32 |
config = config || {}; |
33 |
|
34 |
// Properties |
35 |
this.$indicator = null; |
36 |
this.indicator = null; |
37 |
this.indicatorTitle = null; |
38 |
|
39 |
// Initialization |
40 |
this.setIndicator( config.indicator || this.constructor.static.indicator ); |
41 |
this.setIndicatorTitle( config.indicatorTitle || this.constructor.static.indicatorTitle ); |
42 |
this.setIndicatorElement( config.$indicator || $( '<span>' ) ); |
43 |
}; |
44 |
|
45 |
/* Setup */ |
46 |
|
47 |
OO.initClass( OO.ui.mixin.IndicatorElement ); |
48 |
|
49 |
/* Static Properties */ |
50 |
|
51 |
/** |
52 |
* Symbolic name of the indicator (e.g., ‘clear’ or ‘down’). |
53 |
* The static property will be overridden if the #indicator configuration is used. |
54 |
* |
55 |
* @static |
56 |
* @inheritable |
57 |
* @property {string|null} |
58 |
*/ |
59 |
OO.ui.mixin.IndicatorElement.static.indicator = null; |
60 |
|
61 |
/** |
62 |
* A text string used as the indicator title, a function that returns title text, or `null` |
63 |
* for no title. The static property will be overridden if the #indicatorTitle configuration is used. |
64 |
* |
65 |
* @static |
66 |
* @inheritable |
67 |
* @property {string|Function|null} |
68 |
*/ |
69 |
OO.ui.mixin.IndicatorElement.static.indicatorTitle = null; |
70 |
|
71 |
/* Methods */ |
72 |
|
73 |
/** |
74 |
* Set the indicator element. |
75 |
* |
76 |
* If an element is already set, it will be cleaned up before setting up the new element. |
77 |
* |
78 |
* @param {jQuery} $indicator Element to use as indicator |
79 |
*/ |
80 |
OO.ui.mixin.IndicatorElement.prototype.setIndicatorElement = function ( $indicator ) { |
81 |
if ( this.$indicator ) { |
82 |
this.$indicator |
83 |
.removeClass( 'oo-ui-indicatorElement-indicator oo-ui-indicator-' + this.indicator ) |
84 |
.removeAttr( 'title' ); |
85 |
} |
86 |
|
Error |
Row 87, Column 20: "Prefer classList to $.toggleClass"
jquery/no-class
|
Error |
Row 87, Column 20: "Prefer classList to $.toggleClass"
jquery/no-class
|
Error |
Row 87, Column 20: "Prefer classList to $.addClass"
jquery/no-class
|
87 |
this.$indicator = $indicator |
88 |
.addClass( 'oo-ui-indicatorElement-indicator' ) |
89 |
.toggleClass( 'oo-ui-indicatorElement-noIndicator', !this.indicator ) |
90 |
.toggleClass( 'oo-ui-indicator-' + this.indicator, !!this.indicator ); |
91 |
if ( this.indicatorTitle !== null ) { |
92 |
this.$indicator.attr( 'title', this.indicatorTitle ); |
93 |
} |
94 |
|
95 |
this.updateThemeClasses(); |
96 |
}; |
97 |
|
98 |
/** |
99 |
* Set the indicator by its symbolic name: ‘clear’, ‘down’, ‘required’, ‘search’, ‘up’. Use `null` to remove the indicator. |
100 |
* |
101 |
* @param {string|null} indicator Symbolic name of indicator, or `null` for no indicator |
102 |
* @chainable |
103 |
*/ |
104 |
OO.ui.mixin.IndicatorElement.prototype.setIndicator = function ( indicator ) { |
105 |
indicator = typeof indicator === 'string' && indicator.length ? indicator.trim() : null; |
106 |
|
107 |
if ( this.indicator !== indicator ) { |
108 |
if ( this.$indicator ) { |
109 |
if ( this.indicator !== null ) { |
110 |
this.$indicator.removeClass( 'oo-ui-indicator-' + this.indicator ); |
111 |
} |
112 |
if ( indicator !== null ) { |
113 |
this.$indicator.addClass( 'oo-ui-indicator-' + indicator ); |
114 |
} |
115 |
} |
116 |
this.indicator = indicator; |
117 |
} |
118 |
|
119 |
this.$element.toggleClass( 'oo-ui-indicatorElement', !!this.indicator ); |
120 |
if ( this.$indicator ) { |
121 |
this.$indicator.toggleClass( 'oo-ui-indicatorElement-noIndicator', !this.indicator ); |
122 |
} |
123 |
this.updateThemeClasses(); |
124 |
|
125 |
return this; |
126 |
}; |
127 |
|
128 |
/** |
129 |
* Set the indicator title. |
130 |
* |
131 |
* The title is displayed when a user moves the mouse over the indicator. |
132 |
* |
133 |
* @param {string|Function|null} indicatorTitle Indicator title text, a function that returns text, or |
134 |
* `null` for no indicator title |
135 |
* @chainable |
136 |
*/ |
137 |
OO.ui.mixin.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorTitle ) { |
138 |
indicatorTitle = |
139 |
( typeof indicatorTitle === 'function' || ( typeof indicatorTitle === 'string' && indicatorTitle.length ) ) ? |
140 |
OO.ui.resolveMsg( indicatorTitle ) : null; |
141 |
|
142 |
if ( this.indicatorTitle !== indicatorTitle ) { |
143 |
this.indicatorTitle = indicatorTitle; |
144 |
if ( this.$indicator ) { |
145 |
if ( this.indicatorTitle !== null ) { |
146 |
this.$indicator.attr( 'title', indicatorTitle ); |
147 |
} else { |
148 |
this.$indicator.removeAttr( 'title' ); |
149 |
} |
150 |
} |
151 |
} |
152 |
|
153 |
return this; |
154 |
}; |
155 |
|
156 |
/** |
157 |
* Get the symbolic name of the indicator (e.g., ‘clear’ or ‘down’). |
158 |
* |
159 |
* @return {string} Symbolic name of indicator |
160 |
*/ |
161 |
OO.ui.mixin.IndicatorElement.prototype.getIndicator = function () { |
162 |
return this.indicator; |
163 |
}; |
164 |
|
165 |
/** |
166 |
* Get the indicator title. |
167 |
* |
168 |
* The title is displayed when a user moves the mouse over the indicator. |
169 |
* |
170 |
* @return {string} Indicator title text |
171 |
*/ |
172 |
OO.ui.mixin.IndicatorElement.prototype.getIndicatorTitle = function () { |
173 |
return this.indicatorTitle; |
174 |
}; |
175 |
|
|
|
/src/mixins/ItemWidget.js
|
0 problems
|
|
/src/mixins/LabelElement.js
|
4 problems (4 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 86, Column 3: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 90, Column 4: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 90, Column 4: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 113, Column 16: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* LabelElement is often mixed into other classes to generate a label, which |
3 |
* helps identify the function of an interface element. |
4 |
* See the [OOUI documentation on MediaWiki] [1] for more information. |
5 |
* |
6 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Icons,_Indicators,_and_Labels#Labels |
7 |
* |
8 |
* @abstract |
9 |
* @class |
10 |
* |
11 |
* @constructor |
12 |
* @param {Object} [config] Configuration options |
13 |
* @cfg {jQuery} [$label] The label element created by the class. If this |
14 |
* configuration is omitted, the label element will use a generated `<span>`. |
15 |
* @cfg {jQuery|string|Function|OO.ui.HtmlSnippet} [label] The label text. The label can be specified |
16 |
* as a plaintext string, a jQuery selection of elements, or a function that will produce a string |
17 |
* in the future. See the [OOUI documentation on MediaWiki] [2] for examples. |
18 |
* [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Icons,_Indicators,_and_Labels#Labels |
19 |
*/ |
20 |
OO.ui.mixin.LabelElement = function OoUiMixinLabelElement( config ) { |
21 |
// Configuration initialization |
22 |
config = config || {}; |
23 |
|
24 |
// Properties |
25 |
this.$label = null; |
26 |
this.label = null; |
27 |
|
28 |
// Initialization |
29 |
this.setLabel( config.label || this.constructor.static.label ); |
30 |
this.setLabelElement( config.$label || $( '<span>' ) ); |
31 |
}; |
32 |
|
33 |
/* Setup */ |
34 |
|
35 |
OO.initClass( OO.ui.mixin.LabelElement ); |
36 |
|
37 |
/* Events */ |
38 |
|
39 |
/** |
40 |
* @event labelChange |
41 |
* @param {string} value |
42 |
*/ |
43 |
|
44 |
/* Static Properties */ |
45 |
|
46 |
/** |
47 |
* The label text. The label can be specified as a plaintext string, a function that will |
48 |
* produce a string in the future, or `null` for no label. The static value will |
49 |
* be overridden if a label is specified with the #label config option. |
50 |
* |
51 |
* @static |
52 |
* @inheritable |
53 |
* @property {string|Function|null} |
54 |
*/ |
55 |
OO.ui.mixin.LabelElement.static.label = null; |
56 |
|
57 |
/* Static methods */ |
58 |
|
59 |
/** |
60 |
* Highlight the first occurrence of the query in the given text |
61 |
* |
62 |
* @param {string} text Text |
63 |
* @param {string} query Query to find |
64 |
* @param {Function} [compare] Optional string comparator, e.g. Intl.Collator().compare |
65 |
* @return {jQuery} Text with the first match of the query |
66 |
* sub-string wrapped in highlighted span |
67 |
*/ |
68 |
OO.ui.mixin.LabelElement.static.highlightQuery = function ( text, query, compare ) { |
69 |
var i, tLen, qLen, |
70 |
offset = -1, |
71 |
$result = $( '<span>' ); |
72 |
|
73 |
if ( compare ) { |
74 |
tLen = text.length; |
75 |
qLen = query.length; |
76 |
for ( i = 0; offset === -1 && i <= tLen - qLen; i++ ) { |
77 |
if ( compare( query, text.slice( i, i + qLen ) ) === 0 ) { |
78 |
offset = i; |
79 |
} |
80 |
} |
81 |
} else { |
82 |
offset = text.toLowerCase().indexOf( query.toLowerCase() ); |
83 |
} |
84 |
|
85 |
if ( !query.length || offset === -1 ) { |
Error |
Row 86, Column 3: "Prefer textContent to $.text"
jquery/no-text
|
86 |
$result.text( text ); |
87 |
} else { |
88 |
$result.append( |
89 |
document.createTextNode( text.slice( 0, offset ) ), |
Error |
Row 90, Column 4: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 90, Column 4: "Prefer classList to $.addClass"
jquery/no-class
|
90 |
$( '<span>' ) |
91 |
.addClass( 'oo-ui-labelElement-label-highlight' ) |
92 |
.text( text.slice( offset, offset + query.length ) ), |
93 |
document.createTextNode( text.slice( offset + query.length ) ) |
94 |
); |
95 |
} |
96 |
return $result.contents(); |
97 |
}; |
98 |
|
99 |
/* Methods */ |
100 |
|
101 |
/** |
102 |
* Set the label element. |
103 |
* |
104 |
* If an element is already set, it will be cleaned up before setting up the new element. |
105 |
* |
106 |
* @param {jQuery} $label Element to use as label |
107 |
*/ |
108 |
OO.ui.mixin.LabelElement.prototype.setLabelElement = function ( $label ) { |
109 |
if ( this.$label ) { |
110 |
this.$label.removeClass( 'oo-ui-labelElement-label' ).empty(); |
111 |
} |
112 |
|
Error |
Row 113, Column 16: "Prefer classList to $.addClass"
jquery/no-class
|
113 |
this.$label = $label.addClass( 'oo-ui-labelElement-label' ); |
114 |
this.setLabelContent( this.label ); |
115 |
}; |
116 |
|
117 |
/** |
118 |
* Set the label. |
119 |
* |
120 |
* An empty string will result in the label being hidden. A string containing only whitespace will |
121 |
* be converted to a single ` `. |
122 |
* |
123 |
* @param {jQuery|string|OO.ui.HtmlSnippet|Function|null} label Label nodes; text; a function that returns nodes or |
124 |
* text; or null for no label |
125 |
* @chainable |
126 |
*/ |
127 |
OO.ui.mixin.LabelElement.prototype.setLabel = function ( label ) { |
128 |
label = typeof label === 'function' ? OO.ui.resolveMsg( label ) : label; |
129 |
label = ( ( typeof label === 'string' || label instanceof jQuery ) && label.length ) || ( label instanceof OO.ui.HtmlSnippet && label.toString().length ) ? label : null; |
130 |
|
131 |
if ( this.label !== label ) { |
132 |
if ( this.$label ) { |
133 |
this.setLabelContent( label ); |
134 |
} |
135 |
this.label = label; |
136 |
this.emit( 'labelChange' ); |
137 |
} |
138 |
|
139 |
this.$element.toggleClass( 'oo-ui-labelElement', !!this.label ); |
140 |
|
141 |
return this; |
142 |
}; |
143 |
|
144 |
/** |
145 |
* Set the label as plain text with a highlighted query |
146 |
* |
147 |
* @param {string} text Text label to set |
148 |
* @param {string} query Substring of text to highlight |
149 |
* @param {Function} [compare] Optional string comparator, e.g. Intl.Collator().compare |
150 |
* @chainable |
151 |
*/ |
152 |
OO.ui.mixin.LabelElement.prototype.setHighlightedQuery = function ( text, query, compare ) { |
153 |
return this.setLabel( this.constructor.static.highlightQuery( text, query, compare ) ); |
154 |
}; |
155 |
|
156 |
/** |
157 |
* Get the label. |
158 |
* |
159 |
* @return {jQuery|string|Function|null} Label nodes; text; a function that returns nodes or |
160 |
* text; or null for no label |
161 |
*/ |
162 |
OO.ui.mixin.LabelElement.prototype.getLabel = function () { |
163 |
return this.label; |
164 |
}; |
165 |
|
166 |
/** |
167 |
* Set the content of the label. |
168 |
* |
169 |
* Do not call this method until after the label element has been set by #setLabelElement. |
170 |
* |
171 |
* @private |
172 |
* @param {jQuery|string|Function|null} label Label nodes; text; a function that returns nodes or |
173 |
* text; or null for no label |
174 |
*/ |
175 |
OO.ui.mixin.LabelElement.prototype.setLabelContent = function ( label ) { |
176 |
if ( typeof label === 'string' ) { |
177 |
if ( label.match( /^\s*$/ ) ) { |
178 |
// Convert whitespace only string to a single non-breaking space |
179 |
this.$label.html( ' ' ); |
180 |
} else { |
181 |
this.$label.text( label ); |
182 |
} |
183 |
} else if ( label instanceof OO.ui.HtmlSnippet ) { |
184 |
this.$label.html( label.toString() ); |
185 |
} else if ( label instanceof jQuery ) { |
186 |
this.$label.empty().append( label ); |
187 |
} else { |
188 |
this.$label.empty(); |
189 |
} |
190 |
}; |
191 |
|
|
|
/src/mixins/LookupElement.js
|
0 problems
|
|
/src/mixins/PendingElement.js
|
0 problems
|
|
/src/mixins/RequestManager.js
|
1 problem (1 error, 0 warnings)
|
Line |
Source |
1 |
/** |
2 |
* RequestManager is a mixin that manages the lifecycle of a promise-backed request for a widget, such as |
3 |
* the {@link OO.ui.mixin.LookupElement}. |
4 |
* |
5 |
* @class |
6 |
* @abstract |
7 |
* |
8 |
* @constructor |
9 |
*/ |
10 |
OO.ui.mixin.RequestManager = function OoUiMixinRequestManager() { |
11 |
this.requestCache = {}; |
12 |
this.requestQuery = null; |
13 |
this.requestRequest = null; |
14 |
}; |
15 |
|
16 |
/* Setup */ |
17 |
|
18 |
OO.initClass( OO.ui.mixin.RequestManager ); |
19 |
|
20 |
/** |
21 |
* Get request results for the current query. |
22 |
* |
23 |
* @return {jQuery.Promise} Promise object which will be passed response data as the first argument of |
24 |
* the done event. If the request was aborted to make way for a subsequent request, this promise |
25 |
* may not be rejected, depending on what jQuery feels like doing. |
26 |
*/ |
27 |
OO.ui.mixin.RequestManager.prototype.getRequestData = function () { |
28 |
var widget = this, |
29 |
value = this.getRequestQuery(), |
Error |
Row 30, Column 14: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
30 |
deferred = $.Deferred(), |
31 |
ourRequest; |
32 |
|
33 |
this.abortRequest(); |
34 |
if ( Object.prototype.hasOwnProperty.call( this.requestCache, value ) ) { |
35 |
deferred.resolve( this.requestCache[ value ] ); |
36 |
} else { |
37 |
if ( this.pushPending ) { |
38 |
this.pushPending(); |
39 |
} |
40 |
this.requestQuery = value; |
41 |
ourRequest = this.requestRequest = this.getRequest(); |
42 |
ourRequest |
43 |
.always( function () { |
44 |
// We need to pop pending even if this is an old request, otherwise |
45 |
// the widget will remain pending forever. |
46 |
// TODO: this assumes that an aborted request will fail or succeed soon after |
47 |
// being aborted, or at least eventually. It would be nice if we could popPending() |
48 |
// at abort time, but only if we knew that we hadn't already called popPending() |
49 |
// for that request. |
50 |
if ( widget.popPending ) { |
51 |
widget.popPending(); |
52 |
} |
53 |
} ) |
54 |
.done( function ( response ) { |
55 |
// If this is an old request (and aborting it somehow caused it to still succeed), |
56 |
// ignore its success completely |
57 |
if ( ourRequest === widget.requestRequest ) { |
58 |
widget.requestQuery = null; |
59 |
widget.requestRequest = null; |
60 |
widget.requestCache[ value ] = widget.getRequestCacheDataFromResponse( response ); |
61 |
deferred.resolve( widget.requestCache[ value ] ); |
62 |
} |
63 |
} ) |
64 |
.fail( function () { |
65 |
// If this is an old request (or a request failing because it's being aborted), |
66 |
// ignore its failure completely |
67 |
if ( ourRequest === widget.requestRequest ) { |
68 |
widget.requestQuery = null; |
69 |
widget.requestRequest = null; |
70 |
deferred.reject(); |
71 |
} |
72 |
} ); |
73 |
} |
74 |
return deferred.promise(); |
75 |
}; |
76 |
|
77 |
/** |
78 |
* Abort the currently pending request, if any. |
79 |
* |
80 |
* @private |
81 |
*/ |
82 |
OO.ui.mixin.RequestManager.prototype.abortRequest = function () { |
83 |
var oldRequest = this.requestRequest; |
84 |
if ( oldRequest ) { |
85 |
// First unset this.requestRequest to the fail handler will notice |
86 |
// that the request is no longer current |
87 |
this.requestRequest = null; |
88 |
this.requestQuery = null; |
89 |
oldRequest.abort(); |
90 |
} |
91 |
}; |
92 |
|
93 |
/** |
94 |
* Get the query to be made. |
95 |
* |
96 |
* @protected |
97 |
* @method |
98 |
* @abstract |
99 |
* @return {string} query to be used |
100 |
*/ |
101 |
OO.ui.mixin.RequestManager.prototype.getRequestQuery = null; |
102 |
|
103 |
/** |
104 |
* Get a new request object of the current query value. |
105 |
* |
106 |
* @protected |
107 |
* @method |
108 |
* @abstract |
109 |
* @return {jQuery.Promise} jQuery AJAX object, or promise object with an .abort() method |
110 |
*/ |
111 |
OO.ui.mixin.RequestManager.prototype.getRequest = null; |
112 |
|
113 |
/** |
114 |
* Pre-process data returned by the request from #getRequest. |
115 |
* |
116 |
* The return value of this function will be cached, and any further queries for the given value |
117 |
* will use the cache rather than doing API requests. |
118 |
* |
119 |
* @protected |
120 |
* @method |
121 |
* @abstract |
122 |
* @param {Mixed} response Response from server |
123 |
* @return {Mixed} Cached result data |
124 |
*/ |
125 |
OO.ui.mixin.RequestManager.prototype.getRequestCacheDataFromResponse = null; |
126 |
|
|
|
/src/mixins/TabIndexedElement.js
|
2 problems (2 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 180, Column 13: "Prefer direct property access to $.prop"
jquery/no-prop
|
Error |
Row 182, Column 30: "Prefer getAttribute to $.attr"
jquery/no-attr
|
Line |
Source |
1 |
/** |
2 |
* The TabIndexedElement class is an attribute mixin used to add additional functionality to an |
3 |
* element created by another class. The mixin provides a ‘tabIndex’ property, which specifies the |
4 |
* order in which users will navigate through the focusable elements via the "tab" key. |
5 |
* |
6 |
* @example |
7 |
* // TabIndexedElement is mixed into the ButtonWidget class |
8 |
* // to provide a tabIndex property. |
9 |
* var button1 = new OO.ui.ButtonWidget( { |
10 |
* label: 'fourth', |
11 |
* tabIndex: 4 |
12 |
* } ); |
13 |
* var button2 = new OO.ui.ButtonWidget( { |
14 |
* label: 'second', |
15 |
* tabIndex: 2 |
16 |
* } ); |
17 |
* var button3 = new OO.ui.ButtonWidget( { |
18 |
* label: 'third', |
19 |
* tabIndex: 3 |
20 |
* } ); |
21 |
* var button4 = new OO.ui.ButtonWidget( { |
22 |
* label: 'first', |
23 |
* tabIndex: 1 |
24 |
* } ); |
25 |
* $( 'body' ).append( button1.$element, button2.$element, button3.$element, button4.$element ); |
26 |
* |
27 |
* @abstract |
28 |
* @class |
29 |
* |
30 |
* @constructor |
31 |
* @param {Object} [config] Configuration options |
32 |
* @cfg {jQuery} [$tabIndexed] The element that should use the tabindex functionality. By default, |
33 |
* the functionality is applied to the element created by the class ($element). If a different element is specified, the tabindex |
34 |
* functionality will be applied to it instead. |
35 |
* @cfg {string|number|null} [tabIndex=0] Number that specifies the element’s position in the tab-navigation |
36 |
* order (e.g., 1 for the first focusable element). Use 0 to use the default navigation order; use -1 |
37 |
* to remove the element from the tab-navigation flow. |
38 |
*/ |
39 |
OO.ui.mixin.TabIndexedElement = function OoUiMixinTabIndexedElement( config ) { |
40 |
// Configuration initialization |
41 |
config = $.extend( { tabIndex: 0 }, config ); |
42 |
|
43 |
// Properties |
44 |
this.$tabIndexed = null; |
45 |
this.tabIndex = null; |
46 |
|
47 |
// Events |
48 |
this.connect( this, { disable: 'onTabIndexedElementDisable' } ); |
49 |
|
50 |
// Initialization |
51 |
this.setTabIndex( config.tabIndex ); |
52 |
this.setTabIndexedElement( config.$tabIndexed || this.$element ); |
53 |
}; |
54 |
|
55 |
/* Setup */ |
56 |
|
57 |
OO.initClass( OO.ui.mixin.TabIndexedElement ); |
58 |
|
59 |
/* Methods */ |
60 |
|
61 |
/** |
62 |
* Set the element that should use the tabindex functionality. |
63 |
* |
64 |
* This method is used to retarget a tabindex mixin so that its functionality applies |
65 |
* to the specified element. If an element is currently using the functionality, the mixin’s |
66 |
* effect on that element is removed before the new element is set up. |
67 |
* |
68 |
* @param {jQuery} $tabIndexed Element that should use the tabindex functionality |
69 |
* @chainable |
70 |
*/ |
71 |
OO.ui.mixin.TabIndexedElement.prototype.setTabIndexedElement = function ( $tabIndexed ) { |
72 |
var tabIndex = this.tabIndex; |
73 |
// Remove attributes from old $tabIndexed |
74 |
this.setTabIndex( null ); |
75 |
// Force update of new $tabIndexed |
76 |
this.$tabIndexed = $tabIndexed; |
77 |
this.tabIndex = tabIndex; |
78 |
return this.updateTabIndex(); |
79 |
}; |
80 |
|
81 |
/** |
82 |
* Set the value of the tabindex. |
83 |
* |
84 |
* @param {string|number|null} tabIndex Tabindex value, or `null` for no tabindex |
85 |
* @chainable |
86 |
*/ |
87 |
OO.ui.mixin.TabIndexedElement.prototype.setTabIndex = function ( tabIndex ) { |
88 |
tabIndex = /^-?\d+$/.test( tabIndex ) ? Number( tabIndex ) : null; |
89 |
|
90 |
if ( this.tabIndex !== tabIndex ) { |
91 |
this.tabIndex = tabIndex; |
92 |
this.updateTabIndex(); |
93 |
} |
94 |
|
95 |
return this; |
96 |
}; |
97 |
|
98 |
/** |
99 |
* Update the `tabindex` attribute, in case of changes to tab index or |
100 |
* disabled state. |
101 |
* |
102 |
* @private |
103 |
* @chainable |
104 |
*/ |
105 |
OO.ui.mixin.TabIndexedElement.prototype.updateTabIndex = function () { |
106 |
if ( this.$tabIndexed ) { |
107 |
if ( this.tabIndex !== null ) { |
108 |
// Do not index over disabled elements |
109 |
this.$tabIndexed.attr( { |
110 |
tabindex: this.isDisabled() ? -1 : this.tabIndex, |
111 |
// Support: ChromeVox and NVDA |
112 |
// These do not seem to inherit aria-disabled from parent elements |
113 |
'aria-disabled': this.isDisabled().toString() |
114 |
} ); |
115 |
} else { |
116 |
this.$tabIndexed.removeAttr( 'tabindex aria-disabled' ); |
117 |
} |
118 |
} |
119 |
return this; |
120 |
}; |
121 |
|
122 |
/** |
123 |
* Handle disable events. |
124 |
* |
125 |
* @private |
126 |
* @param {boolean} disabled Element is disabled |
127 |
*/ |
128 |
OO.ui.mixin.TabIndexedElement.prototype.onTabIndexedElementDisable = function () { |
129 |
this.updateTabIndex(); |
130 |
}; |
131 |
|
132 |
/** |
133 |
* Get the value of the tabindex. |
134 |
* |
135 |
* @return {number|null} Tabindex value |
136 |
*/ |
137 |
OO.ui.mixin.TabIndexedElement.prototype.getTabIndex = function () { |
138 |
return this.tabIndex; |
139 |
}; |
140 |
|
141 |
/** |
142 |
* Get an ID of a focusable element of this widget, if any, to be used for `<label for>` value. |
143 |
* |
144 |
* If the element already has an ID then that is returned, otherwise unique ID is |
145 |
* generated, set on the element, and returned. |
146 |
* |
147 |
* @return {string|null} The ID of the focusable element |
148 |
*/ |
149 |
OO.ui.mixin.TabIndexedElement.prototype.getInputId = function () { |
150 |
var id; |
151 |
|
152 |
if ( !this.$tabIndexed ) { |
153 |
return null; |
154 |
} |
155 |
if ( !this.isLabelableNode( this.$tabIndexed ) ) { |
156 |
return null; |
157 |
} |
158 |
|
159 |
id = this.$tabIndexed.attr( 'id' ); |
160 |
if ( id === undefined ) { |
161 |
id = OO.ui.generateElementId(); |
162 |
this.$tabIndexed.attr( 'id', id ); |
163 |
} |
164 |
|
165 |
return id; |
166 |
}; |
167 |
|
168 |
/** |
169 |
* Whether the node is 'labelable' according to the HTML spec |
170 |
* (i.e., whether it can be interacted with through a `<label for="…">`). |
171 |
* See: <https://html.spec.whatwg.org/multipage/forms.html#category-label>. |
172 |
* |
173 |
* @private |
174 |
* @param {jQuery} $node |
175 |
* @return {boolean} |
176 |
*/ |
177 |
OO.ui.mixin.TabIndexedElement.prototype.isLabelableNode = function ( $node ) { |
178 |
var |
179 |
labelableTags = [ 'button', 'meter', 'output', 'progress', 'select', 'textarea' ], |
Error |
Row 180, Column 13: "Prefer direct property access to $.prop"
jquery/no-prop
|
180 |
tagName = $node.prop( 'tagName' ).toLowerCase(); |
181 |
|
Error |
Row 182, Column 30: "Prefer getAttribute to $.attr"
jquery/no-attr
|
182 |
if ( tagName === 'input' && $node.attr( 'type' ) !== 'hidden' ) { |
183 |
return true; |
184 |
} |
185 |
if ( labelableTags.indexOf( tagName ) !== -1 ) { |
186 |
return true; |
187 |
} |
188 |
return false; |
189 |
}; |
190 |
|
191 |
/** |
192 |
* Focus this element. |
193 |
* |
194 |
* @chainable |
195 |
*/ |
196 |
OO.ui.mixin.TabIndexedElement.prototype.focus = function () { |
197 |
if ( !this.isDisabled() ) { |
198 |
this.$tabIndexed.focus(); |
199 |
} |
200 |
return this; |
201 |
}; |
202 |
|
203 |
/** |
204 |
* Blur this element. |
205 |
* |
206 |
* @chainable |
207 |
*/ |
208 |
OO.ui.mixin.TabIndexedElement.prototype.blur = function () { |
209 |
this.$tabIndexed.blur(); |
210 |
return this; |
211 |
}; |
212 |
|
213 |
/** |
214 |
* @inheritdoc OO.ui.Widget |
215 |
*/ |
216 |
OO.ui.mixin.TabIndexedElement.prototype.simulateLabelClick = function () { |
217 |
this.focus(); |
218 |
}; |
219 |
|
|
|
/src/mixins/TitledElement.js
|
0 problems
|
|
/src/Process.js
|
6 problems (6 errors, 0 warnings)
|
Line |
Source |
1 |
/** |
2 |
* A Process is a list of steps that are called in sequence. The step can be a number, a jQuery promise, |
3 |
* or a function: |
4 |
* |
5 |
* - **number**: the process will wait for the specified number of milliseconds before proceeding. |
6 |
* - **promise**: the process will continue to the next step when the promise is successfully resolved |
7 |
* or stop if the promise is rejected. |
8 |
* - **function**: the process will execute the function. The process will stop if the function returns |
9 |
* either a boolean `false` or a promise that is rejected; if the function returns a number, the process |
10 |
* will wait for that number of milliseconds before proceeding. |
11 |
* |
12 |
* If the process fails, an {@link OO.ui.Error error} is generated. Depending on how the error is |
13 |
* configured, users can dismiss the error and try the process again, or not. If a process is stopped, |
14 |
* its remaining steps will not be performed. |
15 |
* |
16 |
* @class |
17 |
* |
18 |
* @constructor |
19 |
* @param {number|jQuery.Promise|Function} step Number of milliseconds to wait before proceeding, promise |
20 |
* that must be resolved before proceeding, or a function to execute. See #createStep for more information. see #createStep for more information |
21 |
* @param {Object} [context=null] Execution context of the function. The context is ignored if the step is |
22 |
* a number or promise. |
23 |
*/ |
24 |
OO.ui.Process = function ( step, context ) { |
25 |
// Properties |
26 |
this.steps = []; |
27 |
|
28 |
// Initialization |
29 |
if ( step !== undefined ) { |
30 |
this.next( step, context ); |
31 |
} |
32 |
}; |
33 |
|
34 |
/* Setup */ |
35 |
|
36 |
OO.initClass( OO.ui.Process ); |
37 |
|
38 |
/* Methods */ |
39 |
|
40 |
/** |
41 |
* Start the process. |
42 |
* |
43 |
* @return {jQuery.Promise} Promise that is resolved when all steps have successfully completed. |
44 |
* If any of the steps return a promise that is rejected or a boolean false, this promise is rejected |
45 |
* and any remaining steps are not performed. |
46 |
*/ |
47 |
OO.ui.Process.prototype.execute = function () { |
48 |
var i, len, promise; |
49 |
|
50 |
/** |
51 |
* Continue execution. |
52 |
* |
53 |
* @ignore |
54 |
* @param {Array} step A function and the context it should be called in |
55 |
* @return {Function} Function that continues the process |
56 |
*/ |
57 |
function proceed( step ) { |
58 |
return function () { |
59 |
// Execute step in the correct context |
60 |
var deferred, |
61 |
result = step.callback.call( step.context ); |
62 |
|
63 |
if ( result === false ) { |
64 |
// Use rejected promise for boolean false results |
Error |
Row 65, Column 12: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
65 |
return $.Deferred().reject( [] ).promise(); |
66 |
} |
67 |
if ( typeof result === 'number' ) { |
68 |
if ( result < 0 ) { |
69 |
throw new Error( 'Cannot go back in time: flux capacitor is out of service' ); |
70 |
} |
71 |
// Use a delayed promise for numbers, expecting them to be in milliseconds |
Error |
Row 72, Column 16: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
72 |
deferred = $.Deferred(); |
73 |
setTimeout( deferred.resolve, result ); |
74 |
return deferred.promise(); |
75 |
} |
76 |
if ( result instanceof OO.ui.Error ) { |
77 |
// Use rejected promise for error |
Error |
Row 78, Column 12: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
78 |
return $.Deferred().reject( [ result ] ).promise(); |
79 |
} |
80 |
if ( Array.isArray( result ) && result.length && result[ 0 ] instanceof OO.ui.Error ) { |
81 |
// Use rejected promise for list of errors |
Error |
Row 82, Column 12: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
82 |
return $.Deferred().reject( result ).promise(); |
83 |
} |
84 |
// Duck-type the object to see if it can produce a promise |
85 |
if ( result && $.isFunction( result.promise ) ) { |
86 |
// Use a promise generated from the result |
87 |
return result.promise(); |
88 |
} |
89 |
// Use resolved promise for other results |
Error |
Row 90, Column 11: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
90 |
return $.Deferred().resolve().promise(); |
91 |
}; |
92 |
} |
93 |
|
94 |
if ( this.steps.length ) { |
95 |
// Generate a chain reaction of promises |
96 |
promise = proceed( this.steps[ 0 ] )(); |
97 |
for ( i = 1, len = this.steps.length; i < len; i++ ) { |
98 |
promise = promise.then( proceed( this.steps[ i ] ) ); |
99 |
} |
100 |
} else { |
Error |
Row 101, Column 13: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
101 |
promise = $.Deferred().resolve().promise(); |
102 |
} |
103 |
|
104 |
return promise; |
105 |
}; |
106 |
|
107 |
/** |
108 |
* Create a process step. |
109 |
* |
110 |
* @private |
111 |
* @param {number|jQuery.Promise|Function} step |
112 |
* |
113 |
* - Number of milliseconds to wait before proceeding |
114 |
* - Promise that must be resolved before proceeding |
115 |
* - Function to execute |
116 |
* - If the function returns a boolean false the process will stop |
117 |
* - If the function returns a promise, the process will continue to the next |
118 |
* step when the promise is resolved or stop if the promise is rejected |
119 |
* - If the function returns a number, the process will wait for that number of |
120 |
* milliseconds before proceeding |
121 |
* @param {Object} [context=null] Execution context of the function. The context is |
122 |
* ignored if the step is a number or promise. |
123 |
* @return {Object} Step object, with `callback` and `context` properties |
124 |
*/ |
125 |
OO.ui.Process.prototype.createStep = function ( step, context ) { |
126 |
if ( typeof step === 'number' || $.isFunction( step.promise ) ) { |
127 |
return { |
128 |
callback: function () { |
129 |
return step; |
130 |
}, |
131 |
context: null |
132 |
}; |
133 |
} |
134 |
if ( $.isFunction( step ) ) { |
135 |
return { |
136 |
callback: step, |
137 |
context: context |
138 |
}; |
139 |
} |
140 |
throw new Error( 'Cannot create process step: number, promise or function expected' ); |
141 |
}; |
142 |
|
143 |
/** |
144 |
* Add step to the beginning of the process. |
145 |
* |
146 |
* @inheritdoc #createStep |
147 |
* @return {OO.ui.Process} this |
148 |
* @chainable |
149 |
*/ |
150 |
OO.ui.Process.prototype.first = function ( step, context ) { |
151 |
this.steps.unshift( this.createStep( step, context ) ); |
152 |
return this; |
153 |
}; |
154 |
|
155 |
/** |
156 |
* Add step to the end of the process. |
157 |
* |
158 |
* @inheritdoc #createStep |
159 |
* @return {OO.ui.Process} this |
160 |
* @chainable |
161 |
*/ |
162 |
OO.ui.Process.prototype.next = function ( step, context ) { |
163 |
this.steps.push( this.createStep( step, context ) ); |
164 |
return this; |
165 |
}; |
166 |
|
|
|
/src/Theme.js
|
2 problems (2 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 51, Column 2: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 51, Column 2: "Prefer classList to $.removeClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* Theme logic. |
3 |
* |
4 |
* @abstract |
5 |
* @class |
6 |
* |
7 |
* @constructor |
8 |
*/ |
9 |
OO.ui.Theme = function OoUiTheme() { |
10 |
this.elementClassesQueue = []; |
11 |
this.debouncedUpdateQueuedElementClasses = OO.ui.debounce( this.updateQueuedElementClasses ); |
12 |
}; |
13 |
|
14 |
/* Setup */ |
15 |
|
16 |
OO.initClass( OO.ui.Theme ); |
17 |
|
18 |
/* Methods */ |
19 |
|
20 |
/** |
21 |
* Get a list of classes to be applied to a widget. |
22 |
* |
23 |
* The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or removes, |
24 |
* otherwise state transitions will not work properly. |
25 |
* |
26 |
* @param {OO.ui.Element} element Element for which to get classes |
27 |
* @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists |
28 |
*/ |
29 |
OO.ui.Theme.prototype.getElementClasses = function () { |
30 |
return { on: [], off: [] }; |
31 |
}; |
32 |
|
33 |
/** |
34 |
* Update CSS classes provided by the theme. |
35 |
* |
36 |
* For elements with theme logic hooks, this should be called any time there's a state change. |
37 |
* |
38 |
* @param {OO.ui.Element} element Element for which to update classes |
39 |
*/ |
40 |
OO.ui.Theme.prototype.updateElementClasses = function ( element ) { |
41 |
var $elements = $( [] ), |
42 |
classes = this.getElementClasses( element ); |
43 |
|
44 |
if ( element.$icon ) { |
45 |
$elements = $elements.add( element.$icon ); |
46 |
} |
47 |
if ( element.$indicator ) { |
48 |
$elements = $elements.add( element.$indicator ); |
49 |
} |
50 |
|
Error |
Row 51, Column 2: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 51, Column 2: "Prefer classList to $.removeClass"
jquery/no-class
|
51 |
$elements |
52 |
.removeClass( classes.off ) |
53 |
.addClass( classes.on ); |
54 |
}; |
55 |
|
56 |
/** |
57 |
* @private |
58 |
*/ |
59 |
OO.ui.Theme.prototype.updateQueuedElementClasses = function () { |
60 |
var i; |
61 |
for ( i = 0; i < this.elementClassesQueue.length; i++ ) { |
62 |
this.updateElementClasses( this.elementClassesQueue[ i ] ); |
63 |
} |
64 |
// Clear the queue |
65 |
this.elementClassesQueue = []; |
66 |
}; |
67 |
|
68 |
/** |
69 |
* Queue #updateElementClasses to be called for this element. |
70 |
* |
71 |
* @localdoc QUnit tests override this method to directly call #queueUpdateElementClasses, |
72 |
* to make them synchronous. |
73 |
* |
74 |
* @param {OO.ui.Element} element Element for which to update classes |
75 |
*/ |
76 |
OO.ui.Theme.prototype.queueUpdateElementClasses = function ( element ) { |
77 |
// Keep items in the queue unique. Use lastIndexOf to start checking from the end because that's |
78 |
// the most common case (this method is often called repeatedly for the same element). |
79 |
if ( this.elementClassesQueue.lastIndexOf( element ) !== -1 ) { |
80 |
return; |
81 |
} |
82 |
this.elementClassesQueue.push( element ); |
83 |
this.debouncedUpdateQueuedElementClasses(); |
84 |
}; |
85 |
|
86 |
/** |
87 |
* Get the transition duration in milliseconds for dialogs opening/closing |
88 |
* |
89 |
* The dialog should be fully rendered this many milliseconds after the |
90 |
* ready process has executed. |
91 |
* |
92 |
* @return {number} Transition duration in milliseconds |
93 |
*/ |
94 |
OO.ui.Theme.prototype.getDialogTransitionDuration = function () { |
95 |
return 0; |
96 |
}; |
97 |
|
|
|
/src/themes/apex/ApexTheme.js
|
0 problems
|
|
/src/themes/blank/BlankTheme.js
|
0 problems
|
|
/src/themes/wikimediaui/WikimediaUITheme.js
|
0 problems
|
|
/src/Tool.js
|
0 problems
|
|
/src/Toolbar.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 391, Column 30: "Prefer closest to $.closest"
jquery/no-closest
|
Line |
Source |
1 |
/** |
2 |
* Toolbars are complex interface components that permit users to easily access a variety |
3 |
* of {@link OO.ui.Tool tools} (e.g., formatting commands) and actions, which are additional commands that are |
4 |
* part of the toolbar, but not configured as tools. |
5 |
* |
6 |
* Individual tools are customized and then registered with a {@link OO.ui.ToolFactory tool factory}, which creates |
7 |
* the tools on demand. Each tool has a symbolic name (used when registering the tool), a title (e.g., ‘Insert |
8 |
* image’), and an icon. |
9 |
* |
10 |
* Individual tools are organized in {@link OO.ui.ToolGroup toolgroups}, which can be {@link OO.ui.MenuToolGroup menus} |
11 |
* of tools, {@link OO.ui.ListToolGroup lists} of tools, or a single {@link OO.ui.BarToolGroup bar} of tools. |
12 |
* The arrangement and order of the toolgroups is customized when the toolbar is set up. Tools can be presented in |
13 |
* any order, but each can only appear once in the toolbar. |
14 |
* |
15 |
* The toolbar can be synchronized with the state of the external "application", like a text |
16 |
* editor's editing area, marking tools as active/inactive (e.g. a 'bold' tool would be shown as |
17 |
* active when the text cursor was inside bolded text) or enabled/disabled (e.g. a table caption |
18 |
* tool would be disabled while the user is not editing a table). A state change is signalled by |
19 |
* emitting the {@link #event-updateState 'updateState' event}, which calls Tools' |
20 |
* {@link OO.ui.Tool#onUpdateState onUpdateState method}. |
21 |
* |
22 |
* The following is an example of a basic toolbar. |
23 |
* |
24 |
* @example |
25 |
* // Example of a toolbar |
26 |
* // Create the toolbar |
27 |
* var toolFactory = new OO.ui.ToolFactory(); |
28 |
* var toolGroupFactory = new OO.ui.ToolGroupFactory(); |
29 |
* var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory ); |
30 |
* |
31 |
* // We will be placing status text in this element when tools are used |
32 |
* var $area = $( '<p>' ).text( 'Toolbar example' ); |
33 |
* |
34 |
* // Define the tools that we're going to place in our toolbar |
35 |
* |
36 |
* // Create a class inheriting from OO.ui.Tool |
37 |
* function SearchTool() { |
38 |
* SearchTool.parent.apply( this, arguments ); |
39 |
* } |
40 |
* OO.inheritClass( SearchTool, OO.ui.Tool ); |
41 |
* // Each tool must have a 'name' (used as an internal identifier, see later) and at least one |
42 |
* // of 'icon' and 'title' (displayed icon and text). |
43 |
* SearchTool.static.name = 'search'; |
44 |
* SearchTool.static.icon = 'search'; |
45 |
* SearchTool.static.title = 'Search...'; |
46 |
* // Defines the action that will happen when this tool is selected (clicked). |
47 |
* SearchTool.prototype.onSelect = function () { |
48 |
* $area.text( 'Search tool clicked!' ); |
49 |
* // Never display this tool as "active" (selected). |
50 |
* this.setActive( false ); |
51 |
* }; |
52 |
* SearchTool.prototype.onUpdateState = function () {}; |
53 |
* // Make this tool available in our toolFactory and thus our toolbar |
54 |
* toolFactory.register( SearchTool ); |
55 |
* |
56 |
* // Register two more tools, nothing interesting here |
57 |
* function SettingsTool() { |
58 |
* SettingsTool.parent.apply( this, arguments ); |
59 |
* } |
60 |
* OO.inheritClass( SettingsTool, OO.ui.Tool ); |
61 |
* SettingsTool.static.name = 'settings'; |
62 |
* SettingsTool.static.icon = 'settings'; |
63 |
* SettingsTool.static.title = 'Change settings'; |
64 |
* SettingsTool.prototype.onSelect = function () { |
65 |
* $area.text( 'Settings tool clicked!' ); |
66 |
* this.setActive( false ); |
67 |
* }; |
68 |
* SettingsTool.prototype.onUpdateState = function () {}; |
69 |
* toolFactory.register( SettingsTool ); |
70 |
* |
71 |
* // Register two more tools, nothing interesting here |
72 |
* function StuffTool() { |
73 |
* StuffTool.parent.apply( this, arguments ); |
74 |
* } |
75 |
* OO.inheritClass( StuffTool, OO.ui.Tool ); |
76 |
* StuffTool.static.name = 'stuff'; |
77 |
* StuffTool.static.icon = 'ellipsis'; |
78 |
* StuffTool.static.title = 'More stuff'; |
79 |
* StuffTool.prototype.onSelect = function () { |
80 |
* $area.text( 'More stuff tool clicked!' ); |
81 |
* this.setActive( false ); |
82 |
* }; |
83 |
* StuffTool.prototype.onUpdateState = function () {}; |
84 |
* toolFactory.register( StuffTool ); |
85 |
* |
86 |
* // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a |
87 |
* // little popup window (a PopupWidget). |
88 |
* function HelpTool( toolGroup, config ) { |
89 |
* OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: { |
90 |
* padded: true, |
91 |
* label: 'Help', |
92 |
* head: true |
93 |
* } }, config ) ); |
94 |
* this.popup.$body.append( '<p>I am helpful!</p>' ); |
95 |
* } |
96 |
* OO.inheritClass( HelpTool, OO.ui.PopupTool ); |
97 |
* HelpTool.static.name = 'help'; |
98 |
* HelpTool.static.icon = 'help'; |
99 |
* HelpTool.static.title = 'Help'; |
100 |
* toolFactory.register( HelpTool ); |
101 |
* |
102 |
* // Finally define which tools and in what order appear in the toolbar. Each tool may only be |
103 |
* // used once (but not all defined tools must be used). |
104 |
* toolbar.setup( [ |
105 |
* { |
106 |
* // 'bar' tool groups display tools' icons only, side-by-side. |
107 |
* type: 'bar', |
108 |
* include: [ 'search', 'help' ] |
109 |
* }, |
110 |
* { |
111 |
* // 'list' tool groups display both the titles and icons, in a dropdown list. |
112 |
* type: 'list', |
113 |
* indicator: 'down', |
114 |
* label: 'More', |
115 |
* include: [ 'settings', 'stuff' ] |
116 |
* } |
117 |
* // Note how the tools themselves are toolgroup-agnostic - the same tool can be displayed |
118 |
* // either in a 'list' or a 'bar'. There is a 'menu' tool group too, not showcased here, |
119 |
* // since it's more complicated to use. (See the next example snippet on this page.) |
120 |
* ] ); |
121 |
* |
122 |
* // Create some UI around the toolbar and place it in the document |
123 |
* var frame = new OO.ui.PanelLayout( { |
124 |
* expanded: false, |
125 |
* framed: true |
126 |
* } ); |
127 |
* var contentFrame = new OO.ui.PanelLayout( { |
128 |
* expanded: false, |
129 |
* padded: true |
130 |
* } ); |
131 |
* frame.$element.append( |
132 |
* toolbar.$element, |
133 |
* contentFrame.$element.append( $area ) |
134 |
* ); |
135 |
* $( 'body' ).append( frame.$element ); |
136 |
* |
137 |
* // Here is where the toolbar is actually built. This must be done after inserting it into the |
138 |
* // document. |
139 |
* toolbar.initialize(); |
140 |
* toolbar.emit( 'updateState' ); |
141 |
* |
142 |
* The following example extends the previous one to illustrate 'menu' toolgroups and the usage of |
143 |
* {@link #event-updateState 'updateState' event}. |
144 |
* |
145 |
* @example |
146 |
* // Create the toolbar |
147 |
* var toolFactory = new OO.ui.ToolFactory(); |
148 |
* var toolGroupFactory = new OO.ui.ToolGroupFactory(); |
149 |
* var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory ); |
150 |
* |
151 |
* // We will be placing status text in this element when tools are used |
152 |
* var $area = $( '<p>' ).text( 'Toolbar example' ); |
153 |
* |
154 |
* // Define the tools that we're going to place in our toolbar |
155 |
* |
156 |
* // Create a class inheriting from OO.ui.Tool |
157 |
* function SearchTool() { |
158 |
* SearchTool.parent.apply( this, arguments ); |
159 |
* } |
160 |
* OO.inheritClass( SearchTool, OO.ui.Tool ); |
161 |
* // Each tool must have a 'name' (used as an internal identifier, see later) and at least one |
162 |
* // of 'icon' and 'title' (displayed icon and text). |
163 |
* SearchTool.static.name = 'search'; |
164 |
* SearchTool.static.icon = 'search'; |
165 |
* SearchTool.static.title = 'Search...'; |
166 |
* // Defines the action that will happen when this tool is selected (clicked). |
167 |
* SearchTool.prototype.onSelect = function () { |
168 |
* $area.text( 'Search tool clicked!' ); |
169 |
* // Never display this tool as "active" (selected). |
170 |
* this.setActive( false ); |
171 |
* }; |
172 |
* SearchTool.prototype.onUpdateState = function () {}; |
173 |
* // Make this tool available in our toolFactory and thus our toolbar |
174 |
* toolFactory.register( SearchTool ); |
175 |
* |
176 |
* // Register two more tools, nothing interesting here |
177 |
* function SettingsTool() { |
178 |
* SettingsTool.parent.apply( this, arguments ); |
179 |
* this.reallyActive = false; |
180 |
* } |
181 |
* OO.inheritClass( SettingsTool, OO.ui.Tool ); |
182 |
* SettingsTool.static.name = 'settings'; |
183 |
* SettingsTool.static.icon = 'settings'; |
184 |
* SettingsTool.static.title = 'Change settings'; |
185 |
* SettingsTool.prototype.onSelect = function () { |
186 |
* $area.text( 'Settings tool clicked!' ); |
187 |
* // Toggle the active state on each click |
188 |
* this.reallyActive = !this.reallyActive; |
189 |
* this.setActive( this.reallyActive ); |
190 |
* // To update the menu label |
191 |
* this.toolbar.emit( 'updateState' ); |
192 |
* }; |
193 |
* SettingsTool.prototype.onUpdateState = function () {}; |
194 |
* toolFactory.register( SettingsTool ); |
195 |
* |
196 |
* // Register two more tools, nothing interesting here |
197 |
* function StuffTool() { |
198 |
* StuffTool.parent.apply( this, arguments ); |
199 |
* this.reallyActive = false; |
200 |
* } |
201 |
* OO.inheritClass( StuffTool, OO.ui.Tool ); |
202 |
* StuffTool.static.name = 'stuff'; |
203 |
* StuffTool.static.icon = 'ellipsis'; |
204 |
* StuffTool.static.title = 'More stuff'; |
205 |
* StuffTool.prototype.onSelect = function () { |
206 |
* $area.text( 'More stuff tool clicked!' ); |
207 |
* // Toggle the active state on each click |
208 |
* this.reallyActive = !this.reallyActive; |
209 |
* this.setActive( this.reallyActive ); |
210 |
* // To update the menu label |
211 |
* this.toolbar.emit( 'updateState' ); |
212 |
* }; |
213 |
* StuffTool.prototype.onUpdateState = function () {}; |
214 |
* toolFactory.register( StuffTool ); |
215 |
* |
216 |
* // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a |
217 |
* // little popup window (a PopupWidget). 'onUpdateState' is also already implemented. |
218 |
* function HelpTool( toolGroup, config ) { |
219 |
* OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: { |
220 |
* padded: true, |
221 |
* label: 'Help', |
222 |
* head: true |
223 |
* } }, config ) ); |
224 |
* this.popup.$body.append( '<p>I am helpful!</p>' ); |
225 |
* } |
226 |
* OO.inheritClass( HelpTool, OO.ui.PopupTool ); |
227 |
* HelpTool.static.name = 'help'; |
228 |
* HelpTool.static.icon = 'help'; |
229 |
* HelpTool.static.title = 'Help'; |
230 |
* toolFactory.register( HelpTool ); |
231 |
* |
232 |
* // Finally define which tools and in what order appear in the toolbar. Each tool may only be |
233 |
* // used once (but not all defined tools must be used). |
234 |
* toolbar.setup( [ |
235 |
* { |
236 |
* // 'bar' tool groups display tools' icons only, side-by-side. |
237 |
* type: 'bar', |
238 |
* include: [ 'search', 'help' ] |
239 |
* }, |
240 |
* { |
241 |
* // 'menu' tool groups display both the titles and icons, in a dropdown menu. |
242 |
* // Menu label indicates which items are selected. |
243 |
* type: 'menu', |
244 |
* indicator: 'down', |
245 |
* include: [ 'settings', 'stuff' ] |
246 |
* } |
247 |
* ] ); |
248 |
* |
249 |
* // Create some UI around the toolbar and place it in the document |
250 |
* var frame = new OO.ui.PanelLayout( { |
251 |
* expanded: false, |
252 |
* framed: true |
253 |
* } ); |
254 |
* var contentFrame = new OO.ui.PanelLayout( { |
255 |
* expanded: false, |
256 |
* padded: true |
257 |
* } ); |
258 |
* frame.$element.append( |
259 |
* toolbar.$element, |
260 |
* contentFrame.$element.append( $area ) |
261 |
* ); |
262 |
* $( 'body' ).append( frame.$element ); |
263 |
* |
264 |
* // Here is where the toolbar is actually built. This must be done after inserting it into the |
265 |
* // document. |
266 |
* toolbar.initialize(); |
267 |
* toolbar.emit( 'updateState' ); |
268 |
* |
269 |
* @class |
270 |
* @extends OO.ui.Element |
271 |
* @mixins OO.EventEmitter |
272 |
* @mixins OO.ui.mixin.GroupElement |
273 |
* |
274 |
* @constructor |
275 |
* @param {OO.ui.ToolFactory} toolFactory Factory for creating tools |
276 |
* @param {OO.ui.ToolGroupFactory} toolGroupFactory Factory for creating toolgroups |
277 |
* @param {Object} [config] Configuration options |
278 |
* @cfg {boolean} [actions] Add an actions section to the toolbar. Actions are commands that are included |
279 |
* in the toolbar, but are not configured as tools. By default, actions are displayed on the right side of |
280 |
* the toolbar. |
281 |
* @cfg {string} [position='top'] Whether the toolbar is positioned above ('top') or below ('bottom') content. |
282 |
* @cfg {jQuery} [$overlay] An overlay for the popup. |
283 |
* See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>. |
284 |
*/ |
285 |
OO.ui.Toolbar = function OoUiToolbar( toolFactory, toolGroupFactory, config ) { |
286 |
// Allow passing positional parameters inside the config object |
287 |
if ( OO.isPlainObject( toolFactory ) && config === undefined ) { |
288 |
config = toolFactory; |
289 |
toolFactory = config.toolFactory; |
290 |
toolGroupFactory = config.toolGroupFactory; |
291 |
} |
292 |
|
293 |
// Configuration initialization |
294 |
config = config || {}; |
295 |
|
296 |
// Parent constructor |
297 |
OO.ui.Toolbar.parent.call( this, config ); |
298 |
|
299 |
// Mixin constructors |
300 |
OO.EventEmitter.call( this ); |
301 |
OO.ui.mixin.GroupElement.call( this, config ); |
302 |
|
303 |
// Properties |
304 |
this.toolFactory = toolFactory; |
305 |
this.toolGroupFactory = toolGroupFactory; |
306 |
this.groupsByName = {}; |
307 |
this.activeToolGroups = 0; |
308 |
this.tools = {}; |
309 |
this.position = config.position || 'top'; |
310 |
this.$bar = $( '<div>' ); |
311 |
this.$actions = $( '<div>' ); |
312 |
this.$popups = $( '<div>' ); |
313 |
this.initialized = false; |
314 |
this.narrowThreshold = null; |
315 |
this.onWindowResizeHandler = this.onWindowResize.bind( this ); |
316 |
this.$overlay = ( config.$overlay === true ? OO.ui.getDefaultOverlay() : config.$overlay ) || this.$element; |
317 |
|
318 |
// Events |
319 |
this.$element |
320 |
.add( this.$bar ).add( this.$group ).add( this.$actions ) |
321 |
.on( 'mousedown keydown', this.onPointerDown.bind( this ) ); |
322 |
|
323 |
// Initialization |
324 |
this.$group.addClass( 'oo-ui-toolbar-tools' ); |
325 |
if ( config.actions ) { |
326 |
this.$bar.append( this.$actions.addClass( 'oo-ui-toolbar-actions' ) ); |
327 |
} |
328 |
this.$popups.addClass( 'oo-ui-toolbar-popups' ); |
329 |
this.$bar |
330 |
.addClass( 'oo-ui-toolbar-bar' ) |
331 |
.append( this.$group, '<div style="clear:both"></div>' ); |
332 |
// Possible classes: oo-ui-toolbar-position-top, oo-ui-toolbar-position-bottom |
333 |
this.$element.addClass( 'oo-ui-toolbar oo-ui-toolbar-position-' + this.position ).append( this.$bar ); |
334 |
this.$overlay.append( this.$popups ); |
335 |
}; |
336 |
|
337 |
/* Setup */ |
338 |
|
339 |
OO.inheritClass( OO.ui.Toolbar, OO.ui.Element ); |
340 |
OO.mixinClass( OO.ui.Toolbar, OO.EventEmitter ); |
341 |
OO.mixinClass( OO.ui.Toolbar, OO.ui.mixin.GroupElement ); |
342 |
|
343 |
/* Events */ |
344 |
|
345 |
/** |
346 |
* @event updateState |
347 |
* |
348 |
* An 'updateState' event must be emitted on the Toolbar (by calling `toolbar.emit( 'updateState' )`) |
349 |
* every time the state of the application using the toolbar changes, and an update to the state of |
350 |
* tools is required. |
351 |
* |
352 |
* @param {...Mixed} data Application-defined parameters |
353 |
*/ |
354 |
|
355 |
/** |
356 |
* @event active |
357 |
* |
358 |
* An 'active' event is emitted when the number of active toolgroups increases from 0, or |
359 |
* returns to 0. |
360 |
* |
361 |
* @param {boolean} There are active toolgroups in this toolbar |
362 |
*/ |
363 |
|
364 |
/* Methods */ |
365 |
|
366 |
/** |
367 |
* Get the tool factory. |
368 |
* |
369 |
* @return {OO.ui.ToolFactory} Tool factory |
370 |
*/ |
371 |
OO.ui.Toolbar.prototype.getToolFactory = function () { |
372 |
return this.toolFactory; |
373 |
}; |
374 |
|
375 |
/** |
376 |
* Get the toolgroup factory. |
377 |
* |
378 |
* @return {OO.Factory} Toolgroup factory |
379 |
*/ |
380 |
OO.ui.Toolbar.prototype.getToolGroupFactory = function () { |
381 |
return this.toolGroupFactory; |
382 |
}; |
383 |
|
384 |
/** |
385 |
* Handles mouse down events. |
386 |
* |
387 |
* @private |
388 |
* @param {jQuery.Event} e Mouse down event |
389 |
*/ |
390 |
OO.ui.Toolbar.prototype.onPointerDown = function ( e ) { |
Error |
Row 391, Column 30: "Prefer closest to $.closest"
jquery/no-closest
|
391 |
var $closestWidgetToEvent = $( e.target ).closest( '.oo-ui-widget' ), |
392 |
$closestWidgetToToolbar = this.$element.closest( '.oo-ui-widget' ); |
393 |
if ( !$closestWidgetToEvent.length || $closestWidgetToEvent[ 0 ] === $closestWidgetToToolbar[ 0 ] ) { |
394 |
return false; |
395 |
} |
396 |
}; |
397 |
|
398 |
/** |
399 |
* Handle window resize event. |
400 |
* |
401 |
* @private |
402 |
* @param {jQuery.Event} e Window resize event |
403 |
*/ |
404 |
OO.ui.Toolbar.prototype.onWindowResize = function () { |
405 |
this.$element.add( this.$popups ).toggleClass( |
406 |
'oo-ui-toolbar-narrow', |
407 |
this.$bar[ 0 ].clientWidth <= this.getNarrowThreshold() |
408 |
); |
409 |
}; |
410 |
|
411 |
/** |
412 |
* Get the (lazily-computed) width threshold for applying the oo-ui-toolbar-narrow |
413 |
* class. |
414 |
* |
415 |
* @private |
416 |
* @return {number} Width threshold in pixels |
417 |
*/ |
418 |
OO.ui.Toolbar.prototype.getNarrowThreshold = function () { |
419 |
if ( this.narrowThreshold === null ) { |
420 |
this.narrowThreshold = this.$group[ 0 ].offsetWidth + this.$actions[ 0 ].offsetWidth; |
421 |
} |
422 |
return this.narrowThreshold; |
423 |
}; |
424 |
|
425 |
/** |
426 |
* Sets up handles and preloads required information for the toolbar to work. |
427 |
* This must be called after it is attached to a visible document and before doing anything else. |
428 |
*/ |
429 |
OO.ui.Toolbar.prototype.initialize = function () { |
430 |
if ( !this.initialized ) { |
431 |
this.initialized = true; |
432 |
$( this.getElementWindow() ).on( 'resize', this.onWindowResizeHandler ); |
433 |
this.onWindowResize(); |
434 |
} |
435 |
}; |
436 |
|
437 |
/** |
438 |
* Set up the toolbar. |
439 |
* |
440 |
* The toolbar is set up with a list of toolgroup configurations that specify the type of |
441 |
* toolgroup ({@link OO.ui.BarToolGroup bar}, {@link OO.ui.MenuToolGroup menu}, or {@link OO.ui.ListToolGroup list}) |
442 |
* to add and which tools to include, exclude, promote, or demote within that toolgroup. Please |
443 |
* see {@link OO.ui.ToolGroup toolgroups} for more information about including tools in toolgroups. |
444 |
* |
445 |
* @param {Object.<string,Array>} groups List of toolgroup configurations |
446 |
* @param {string} [groups.name] Symbolic name for this toolgroup |
447 |
* @param {string} [groups.type] Toolgroup type, should exist in the toolgroup factory |
448 |
* @param {Array|string} [groups.include] Tools to include in the toolgroup |
449 |
* @param {Array|string} [groups.exclude] Tools to exclude from the toolgroup |
450 |
* @param {Array|string} [groups.promote] Tools to promote to the beginning of the toolgroup |
451 |
* @param {Array|string} [groups.demote] Tools to demote to the end of the toolgroup |
452 |
*/ |
453 |
OO.ui.Toolbar.prototype.setup = function ( groups ) { |
454 |
var i, len, type, toolGroup, groupConfig, |
455 |
items = [], |
456 |
defaultType = 'bar'; |
457 |
|
458 |
// Cleanup previous groups |
459 |
this.reset(); |
460 |
|
461 |
// Build out new groups |
462 |
for ( i = 0, len = groups.length; i < len; i++ ) { |
463 |
groupConfig = groups[ i ]; |
464 |
if ( groupConfig.include === '*' ) { |
465 |
// Apply defaults to catch-all groups |
466 |
if ( groupConfig.type === undefined ) { |
467 |
groupConfig.type = 'list'; |
468 |
} |
469 |
if ( groupConfig.label === undefined ) { |
470 |
groupConfig.label = OO.ui.msg( 'ooui-toolbar-more' ); |
471 |
} |
472 |
} |
473 |
// Check type has been registered |
474 |
type = this.getToolGroupFactory().lookup( groupConfig.type ) ? groupConfig.type : defaultType; |
475 |
toolGroup = this.getToolGroupFactory().create( type, this, groupConfig ); |
476 |
items.push( toolGroup ); |
477 |
if ( groupConfig.name ) { |
478 |
this.groupsByName[ groupConfig.name ] = toolGroup; |
479 |
} else { |
480 |
// Groups without name are deprecated |
481 |
OO.ui.warnDeprecation( 'Toolgroups must have a \'name\' property' ); |
482 |
} |
483 |
toolGroup.connect( this, { active: 'onToolGroupActive' } ); |
484 |
} |
485 |
this.addItems( items ); |
486 |
}; |
487 |
|
488 |
/** |
489 |
* Handle active events from tool groups |
490 |
* |
491 |
* @param {boolean} active Tool group has become active, inactive if false |
492 |
* @fires active |
493 |
*/ |
494 |
OO.ui.Toolbar.prototype.onToolGroupActive = function ( active ) { |
495 |
if ( active ) { |
496 |
this.activeToolGroups++; |
497 |
if ( this.activeToolGroups === 1 ) { |
498 |
this.emit( 'active', true ); |
499 |
} |
500 |
} else { |
501 |
this.activeToolGroups--; |
502 |
if ( this.activeToolGroups === 0 ) { |
503 |
this.emit( 'active', false ); |
504 |
} |
505 |
} |
506 |
}; |
507 |
|
508 |
/** |
509 |
* Get a toolgroup by name |
510 |
* |
511 |
* @param {string} name Group name |
512 |
* @return {OO.ui.ToolGroup|null} Tool group, or null if none found by that name |
513 |
*/ |
514 |
OO.ui.Toolbar.prototype.getToolGroupByName = function ( name ) { |
515 |
return this.groupsByName[ name ] || null; |
516 |
}; |
517 |
|
518 |
/** |
519 |
* Remove all tools and toolgroups from the toolbar. |
520 |
*/ |
521 |
OO.ui.Toolbar.prototype.reset = function () { |
522 |
var i, len; |
523 |
|
524 |
this.groupsByName = {}; |
525 |
this.tools = {}; |
526 |
for ( i = 0, len = this.items.length; i < len; i++ ) { |
527 |
this.items[ i ].destroy(); |
528 |
} |
529 |
this.clearItems(); |
530 |
}; |
531 |
|
532 |
/** |
533 |
* Destroy the toolbar. |
534 |
* |
535 |
* Destroying the toolbar removes all event handlers and DOM elements that constitute the toolbar. Call |
536 |
* this method whenever you are done using a toolbar. |
537 |
*/ |
538 |
OO.ui.Toolbar.prototype.destroy = function () { |
539 |
$( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler ); |
540 |
this.reset(); |
541 |
this.$element.remove(); |
542 |
}; |
543 |
|
544 |
/** |
545 |
* Check if the tool is available. |
546 |
* |
547 |
* Available tools are ones that have not yet been added to the toolbar. |
548 |
* |
549 |
* @param {string} name Symbolic name of tool |
550 |
* @return {boolean} Tool is available |
551 |
*/ |
552 |
OO.ui.Toolbar.prototype.isToolAvailable = function ( name ) { |
553 |
return !this.tools[ name ]; |
554 |
}; |
555 |
|
556 |
/** |
557 |
* Prevent tool from being used again. |
558 |
* |
559 |
* @param {OO.ui.Tool} tool Tool to reserve |
560 |
*/ |
561 |
OO.ui.Toolbar.prototype.reserveTool = function ( tool ) { |
562 |
this.tools[ tool.getName() ] = tool; |
563 |
}; |
564 |
|
565 |
/** |
566 |
* Allow tool to be used again. |
567 |
* |
568 |
* @param {OO.ui.Tool} tool Tool to release |
569 |
*/ |
570 |
OO.ui.Toolbar.prototype.releaseTool = function ( tool ) { |
571 |
delete this.tools[ tool.getName() ]; |
572 |
}; |
573 |
|
574 |
/** |
575 |
* Get accelerator label for tool. |
576 |
* |
577 |
* The OOUI library does not contain an accelerator system, but this is the hook for one. To |
578 |
* use an accelerator system, subclass the toolbar and override this method, which is meant to return a label |
579 |
* that describes the accelerator keys for the tool passed (by symbolic name) to the method. |
580 |
* |
581 |
* @param {string} name Symbolic name of tool |
582 |
* @return {string|undefined} Tool accelerator label if available |
583 |
*/ |
584 |
OO.ui.Toolbar.prototype.getToolAccelerator = function () { |
585 |
return undefined; |
586 |
}; |
587 |
|
|
|
/src/ToolFactory.js
|
0 problems
|
|
/src/ToolGroup.js
|
3 problems (3 errors, 0 warnings)
|
Line |
Source |
1 |
/** |
2 |
* ToolGroups are collections of {@link OO.ui.Tool tools} that are used in a {@link OO.ui.Toolbar toolbar}. |
3 |
* The type of toolgroup ({@link OO.ui.ListToolGroup list}, {@link OO.ui.BarToolGroup bar}, or {@link OO.ui.MenuToolGroup menu}) |
4 |
* to which a tool belongs determines how the tool is arranged and displayed in the toolbar. Toolgroups |
5 |
* themselves are created on demand with a {@link OO.ui.ToolGroupFactory toolgroup factory}. |
6 |
* |
7 |
* Toolgroups can contain individual tools, groups of tools, or all available tools, as specified |
8 |
* using the `include` config option. See OO.ui.ToolFactory#extract on documentation of the format. |
9 |
* The options `exclude`, `promote`, and `demote` support the same formats. |
10 |
* |
11 |
* See {@link OO.ui.Toolbar toolbars} for a full example. For more information about toolbars in general, |
12 |
* please see the [OOUI documentation on MediaWiki][1]. |
13 |
* |
14 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars |
15 |
* |
16 |
* @abstract |
17 |
* @class |
18 |
* @extends OO.ui.Widget |
19 |
* @mixins OO.ui.mixin.GroupElement |
20 |
* |
21 |
* @constructor |
22 |
* @param {OO.ui.Toolbar} toolbar |
23 |
* @param {Object} [config] Configuration options |
24 |
* @cfg {Array|string} [include] List of tools to include in the toolgroup, see above. |
25 |
* @cfg {Array|string} [exclude] List of tools to exclude from the toolgroup, see above. |
26 |
* @cfg {Array|string} [promote] List of tools to promote to the beginning of the toolgroup, see above. |
27 |
* @cfg {Array|string} [demote] List of tools to demote to the end of the toolgroup, see above. |
28 |
* This setting is particularly useful when tools have been added to the toolgroup |
29 |
* en masse (e.g., via the catch-all selector). |
30 |
*/ |
31 |
OO.ui.ToolGroup = function OoUiToolGroup( toolbar, config ) { |
32 |
// Allow passing positional parameters inside the config object |
33 |
if ( OO.isPlainObject( toolbar ) && config === undefined ) { |
34 |
config = toolbar; |
35 |
toolbar = config.toolbar; |
36 |
} |
37 |
|
38 |
// Configuration initialization |
39 |
config = config || {}; |
40 |
|
41 |
// Parent constructor |
42 |
OO.ui.ToolGroup.parent.call( this, config ); |
43 |
|
44 |
// Mixin constructors |
45 |
OO.ui.mixin.GroupElement.call( this, config ); |
46 |
|
47 |
// Properties |
48 |
this.toolbar = toolbar; |
49 |
this.tools = {}; |
50 |
this.pressed = null; |
51 |
this.autoDisabled = false; |
52 |
this.include = config.include || []; |
53 |
this.exclude = config.exclude || []; |
54 |
this.promote = config.promote || []; |
55 |
this.demote = config.demote || []; |
56 |
this.onDocumentMouseKeyUpHandler = this.onDocumentMouseKeyUp.bind( this ); |
57 |
|
58 |
// Events |
59 |
this.$group.on( { |
60 |
mousedown: this.onMouseKeyDown.bind( this ), |
61 |
mouseup: this.onMouseKeyUp.bind( this ), |
62 |
keydown: this.onMouseKeyDown.bind( this ), |
63 |
keyup: this.onMouseKeyUp.bind( this ), |
64 |
focus: this.onMouseOverFocus.bind( this ), |
65 |
blur: this.onMouseOutBlur.bind( this ), |
66 |
mouseover: this.onMouseOverFocus.bind( this ), |
67 |
mouseout: this.onMouseOutBlur.bind( this ) |
68 |
} ); |
69 |
this.toolbar.getToolFactory().connect( this, { register: 'onToolFactoryRegister' } ); |
70 |
this.aggregate( { disable: 'itemDisable' } ); |
71 |
this.connect( this, { |
72 |
itemDisable: 'updateDisabled', |
73 |
disable: 'onDisable' |
74 |
} ); |
75 |
|
76 |
// Initialization |
77 |
this.$group.addClass( 'oo-ui-toolGroup-tools' ); |
78 |
this.$element |
79 |
.addClass( 'oo-ui-toolGroup' ) |
80 |
.append( this.$group ); |
81 |
this.onDisable( this.isDisabled() ); |
82 |
this.populate(); |
83 |
}; |
84 |
|
85 |
/* Setup */ |
86 |
|
87 |
OO.inheritClass( OO.ui.ToolGroup, OO.ui.Widget ); |
88 |
OO.mixinClass( OO.ui.ToolGroup, OO.ui.mixin.GroupElement ); |
89 |
|
90 |
/* Events */ |
91 |
|
92 |
/** |
93 |
* @event update |
94 |
*/ |
95 |
|
96 |
/** |
97 |
* @event active |
98 |
* |
99 |
* An 'active' event is emitted when any popup is shown/hidden. |
100 |
* |
101 |
* @param {boolean} The popup is visible |
102 |
*/ |
103 |
|
104 |
/* Static Properties */ |
105 |
|
106 |
/** |
107 |
* Show labels in tooltips. |
108 |
* |
109 |
* @static |
110 |
* @inheritable |
111 |
* @property {boolean} |
112 |
*/ |
113 |
OO.ui.ToolGroup.static.titleTooltips = false; |
114 |
|
115 |
/** |
116 |
* Show acceleration labels in tooltips. |
117 |
* |
118 |
* Note: The OOUI library does not include an accelerator system, but does contain |
119 |
* a hook for one. To use an accelerator system, subclass the {@link OO.ui.Toolbar toolbar} and |
120 |
* override the {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method, which is |
121 |
* meant to return a label that describes the accelerator keys for a given tool (e.g., 'Ctrl + M'). |
122 |
* |
123 |
* @static |
124 |
* @inheritable |
125 |
* @property {boolean} |
126 |
*/ |
127 |
OO.ui.ToolGroup.static.accelTooltips = false; |
128 |
|
129 |
/** |
130 |
* Automatically disable the toolgroup when all tools are disabled |
131 |
* |
132 |
* @static |
133 |
* @inheritable |
134 |
* @property {boolean} |
135 |
*/ |
136 |
OO.ui.ToolGroup.static.autoDisable = true; |
137 |
|
138 |
/** |
139 |
* @abstract |
140 |
* @static |
141 |
* @inheritable |
142 |
* @property {string} |
143 |
*/ |
144 |
OO.ui.ToolGroup.static.name = null; |
145 |
|
146 |
/* Methods */ |
147 |
|
148 |
/** |
149 |
* @inheritdoc |
150 |
*/ |
151 |
OO.ui.ToolGroup.prototype.isDisabled = function () { |
152 |
return this.autoDisabled || OO.ui.ToolGroup.parent.prototype.isDisabled.apply( this, arguments ); |
153 |
}; |
154 |
|
155 |
/** |
156 |
* @inheritdoc |
157 |
*/ |
158 |
OO.ui.ToolGroup.prototype.updateDisabled = function () { |
159 |
var i, item, allDisabled = true; |
160 |
|
161 |
if ( this.constructor.static.autoDisable ) { |
162 |
for ( i = this.items.length - 1; i >= 0; i-- ) { |
163 |
item = this.items[ i ]; |
164 |
if ( !item.isDisabled() ) { |
165 |
allDisabled = false; |
166 |
break; |
167 |
} |
168 |
} |
169 |
this.autoDisabled = allDisabled; |
170 |
} |
171 |
OO.ui.ToolGroup.parent.prototype.updateDisabled.apply( this, arguments ); |
172 |
}; |
173 |
|
174 |
/** |
175 |
* Handle disable events. |
176 |
* |
177 |
* @protected |
178 |
* @param {boolean} isDisabled |
179 |
*/ |
180 |
OO.ui.ToolGroup.prototype.onDisable = function ( isDisabled ) { |
181 |
this.$group.toggleClass( 'oo-ui-toolGroup-disabled-tools', isDisabled ); |
182 |
this.$group.toggleClass( 'oo-ui-toolGroup-enabled-tools', !isDisabled ); |
183 |
}; |
184 |
|
185 |
/** |
186 |
* Handle mouse down and key down events. |
187 |
* |
188 |
* @protected |
189 |
* @param {jQuery.Event} e Mouse down or key down event |
190 |
*/ |
191 |
OO.ui.ToolGroup.prototype.onMouseKeyDown = function ( e ) { |
192 |
if ( |
193 |
!this.isDisabled() && |
194 |
( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) |
195 |
) { |
196 |
this.pressed = this.findTargetTool( e ); |
197 |
if ( this.pressed ) { |
198 |
this.pressed.setActive( true ); |
199 |
this.getElementDocument().addEventListener( 'mouseup', this.onDocumentMouseKeyUpHandler, true ); |
200 |
this.getElementDocument().addEventListener( 'keyup', this.onDocumentMouseKeyUpHandler, true ); |
201 |
return false; |
202 |
} |
203 |
} |
204 |
}; |
205 |
|
206 |
/** |
207 |
* Handle document mouse up and key up events. |
208 |
* |
209 |
* @protected |
210 |
* @param {MouseEvent|KeyboardEvent} e Mouse up or key up event |
211 |
*/ |
212 |
OO.ui.ToolGroup.prototype.onDocumentMouseKeyUp = function ( e ) { |
213 |
this.getElementDocument().removeEventListener( 'mouseup', this.onDocumentMouseKeyUpHandler, true ); |
214 |
this.getElementDocument().removeEventListener( 'keyup', this.onDocumentMouseKeyUpHandler, true ); |
215 |
// onMouseKeyUp may be called a second time, depending on where the mouse is when the button is |
216 |
// released, but since `this.pressed` will no longer be true, the second call will be ignored. |
217 |
this.onMouseKeyUp( e ); |
218 |
}; |
219 |
|
220 |
// Deprecated alias since 0.28.3 |
221 |
OO.ui.ToolGroup.prototype.onCapturedMouseKeyUp = function () { |
222 |
OO.ui.warnDeprecation( 'onCapturedMouseKeyUp is deprecated, use onDocumentMouseKeyUp instead' ); |
223 |
this.onDocumentMouseKeyUp.apply( this, arguments ); |
224 |
}; |
225 |
|
226 |
/** |
227 |
* Handle mouse up and key up events. |
228 |
* |
229 |
* @protected |
230 |
* @param {MouseEvent|KeyboardEvent} e Mouse up or key up event |
231 |
*/ |
232 |
OO.ui.ToolGroup.prototype.onMouseKeyUp = function ( e ) { |
233 |
var tool = this.findTargetTool( e ); |
234 |
|
235 |
if ( |
236 |
!this.isDisabled() && this.pressed && this.pressed === tool && |
237 |
( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) |
238 |
) { |
239 |
this.pressed.onSelect(); |
240 |
this.pressed = null; |
241 |
e.preventDefault(); |
242 |
e.stopPropagation(); |
243 |
} |
244 |
|
245 |
this.pressed = null; |
246 |
}; |
247 |
|
248 |
/** |
249 |
* Handle mouse over and focus events. |
250 |
* |
251 |
* @protected |
252 |
* @param {jQuery.Event} e Mouse over or focus event |
253 |
*/ |
254 |
OO.ui.ToolGroup.prototype.onMouseOverFocus = function ( e ) { |
255 |
var tool = this.findTargetTool( e ); |
256 |
|
257 |
if ( this.pressed && this.pressed === tool ) { |
258 |
this.pressed.setActive( true ); |
259 |
} |
260 |
}; |
261 |
|
262 |
/** |
263 |
* Handle mouse out and blur events. |
264 |
* |
265 |
* @protected |
266 |
* @param {jQuery.Event} e Mouse out or blur event |
267 |
*/ |
268 |
OO.ui.ToolGroup.prototype.onMouseOutBlur = function ( e ) { |
269 |
var tool = this.findTargetTool( e ); |
270 |
|
271 |
if ( this.pressed && this.pressed === tool ) { |
272 |
this.pressed.setActive( false ); |
273 |
} |
274 |
}; |
275 |
|
276 |
/** |
277 |
* Get the closest tool to a jQuery.Event. |
278 |
* |
279 |
* Only tool links are considered, which prevents other elements in the tool such as popups from |
280 |
* triggering tool group interactions. |
281 |
* |
282 |
* @private |
283 |
* @param {jQuery.Event} e |
284 |
* @return {OO.ui.Tool|null} Tool, `null` if none was found |
285 |
*/ |
286 |
OO.ui.ToolGroup.prototype.findTargetTool = function ( e ) { |
287 |
var tool, |
Error |
Row 288, Column 11: "Prefer closest to $.closest"
jquery/no-closest
|
288 |
$item = $( e.target ).closest( '.oo-ui-tool-link' ); |
289 |
|
290 |
if ( $item.length ) { |
Error |
Row 291, Column 10: "Prefer WeakMap to $.data"
jquery/no-data
|
Error |
Row 291, Column 10: "Prefer parentElement to $.parent"
jquery/no-parent
|
291 |
tool = $item.parent().data( 'oo-ui-tool' ); |
292 |
} |
293 |
|
294 |
return tool && !tool.isDisabled() ? tool : null; |
295 |
}; |
296 |
|
297 |
/** |
298 |
* Handle tool registry register events. |
299 |
* |
300 |
* If a tool is registered after the group is created, we must repopulate the list to account for: |
301 |
* |
302 |
* - a tool being added that may be included |
303 |
* - a tool already included being overridden |
304 |
* |
305 |
* @protected |
306 |
* @param {string} name Symbolic name of tool |
307 |
*/ |
308 |
OO.ui.ToolGroup.prototype.onToolFactoryRegister = function () { |
309 |
this.populate(); |
310 |
}; |
311 |
|
312 |
/** |
313 |
* Get the toolbar that contains the toolgroup. |
314 |
* |
315 |
* @return {OO.ui.Toolbar} Toolbar that contains the toolgroup |
316 |
*/ |
317 |
OO.ui.ToolGroup.prototype.getToolbar = function () { |
318 |
return this.toolbar; |
319 |
}; |
320 |
|
321 |
/** |
322 |
* Add and remove tools based on configuration. |
323 |
*/ |
324 |
OO.ui.ToolGroup.prototype.populate = function () { |
325 |
var i, len, name, tool, |
326 |
toolFactory = this.toolbar.getToolFactory(), |
327 |
names = {}, |
328 |
add = [], |
329 |
remove = [], |
330 |
list = this.toolbar.getToolFactory().getTools( |
331 |
this.include, this.exclude, this.promote, this.demote |
332 |
); |
333 |
|
334 |
// Build a list of needed tools |
335 |
for ( i = 0, len = list.length; i < len; i++ ) { |
336 |
name = list[ i ]; |
337 |
if ( |
338 |
// Tool exists |
339 |
toolFactory.lookup( name ) && |
340 |
// Tool is available or is already in this group |
341 |
( this.toolbar.isToolAvailable( name ) || this.tools[ name ] ) |
342 |
) { |
343 |
// Hack to prevent infinite recursion via ToolGroupTool. We need to reserve the tool before |
344 |
// creating it, but we can't call reserveTool() yet because we haven't created the tool. |
345 |
this.toolbar.tools[ name ] = true; |
346 |
tool = this.tools[ name ]; |
347 |
if ( !tool ) { |
348 |
// Auto-initialize tools on first use |
349 |
this.tools[ name ] = tool = toolFactory.create( name, this ); |
350 |
tool.updateTitle(); |
351 |
} |
352 |
this.toolbar.reserveTool( tool ); |
353 |
add.push( tool ); |
354 |
names[ name ] = true; |
355 |
} |
356 |
} |
357 |
// Remove tools that are no longer needed |
358 |
for ( name in this.tools ) { |
359 |
if ( !names[ name ] ) { |
360 |
this.tools[ name ].destroy(); |
361 |
this.toolbar.releaseTool( this.tools[ name ] ); |
362 |
remove.push( this.tools[ name ] ); |
363 |
delete this.tools[ name ]; |
364 |
} |
365 |
} |
366 |
if ( remove.length ) { |
367 |
this.removeItems( remove ); |
368 |
} |
369 |
// Update emptiness state |
370 |
if ( add.length ) { |
371 |
this.$element.removeClass( 'oo-ui-toolGroup-empty' ); |
372 |
} else { |
373 |
this.$element.addClass( 'oo-ui-toolGroup-empty' ); |
374 |
} |
375 |
// Re-add tools (moving existing ones to new locations) |
376 |
this.addItems( add ); |
377 |
// Disabled state may depend on items |
378 |
this.updateDisabled(); |
379 |
}; |
380 |
|
381 |
/** |
382 |
* Destroy toolgroup. |
383 |
*/ |
384 |
OO.ui.ToolGroup.prototype.destroy = function () { |
385 |
var name; |
386 |
|
387 |
this.clearItems(); |
388 |
this.toolbar.getToolFactory().disconnect( this ); |
389 |
for ( name in this.tools ) { |
390 |
this.toolbar.releaseTool( this.tools[ name ] ); |
391 |
this.tools[ name ].disconnect( this ).destroy(); |
392 |
delete this.tools[ name ]; |
393 |
} |
394 |
this.$element.remove(); |
395 |
}; |
396 |
|
|
|
/src/ToolGroupFactory.js
|
0 problems
|
|
/src/toolgroups/BarToolGroup.js
|
0 problems
|
|
/src/toolgroups/ListToolGroup.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 197, Column 3: "Prefer closest to $.closest"
jquery/no-closest
|
Line |
Source |
1 |
/** |
2 |
* ListToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to |
3 |
* create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.MenuToolGroup MenuToolGroup} |
4 |
* and {@link OO.ui.BarToolGroup BarToolGroup}). The {@link OO.ui.Tool tools} in a ListToolGroup are displayed |
5 |
* by label in a dropdown menu. The title of the tool is used as the label text. The menu itself can be configured |
6 |
* with a label, icon, indicator, header, and title. |
7 |
* |
8 |
* ListToolGroups can be configured to be expanded and collapsed. Collapsed lists will have a ‘More’ option that |
9 |
* users can select to see the full list of tools. If a collapsed toolgroup is expanded, a ‘Fewer’ option permits |
10 |
* users to collapse the list again. |
11 |
* |
12 |
* ListToolGroups are created by a {@link OO.ui.ToolGroupFactory toolgroup factory} when the toolbar is set up. The factory |
13 |
* requires the ListToolGroup's symbolic name, 'list', which is specified along with the other configurations. For more |
14 |
* information about how to add tools to a ListToolGroup, please see {@link OO.ui.ToolGroup toolgroup}. |
15 |
* |
16 |
* @example |
17 |
* // Example of a ListToolGroup |
18 |
* var toolFactory = new OO.ui.ToolFactory(); |
19 |
* var toolGroupFactory = new OO.ui.ToolGroupFactory(); |
20 |
* var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory ); |
21 |
* |
22 |
* // Configure and register two tools |
23 |
* function SettingsTool() { |
24 |
* SettingsTool.parent.apply( this, arguments ); |
25 |
* } |
26 |
* OO.inheritClass( SettingsTool, OO.ui.Tool ); |
27 |
* SettingsTool.static.name = 'settings'; |
28 |
* SettingsTool.static.icon = 'settings'; |
29 |
* SettingsTool.static.title = 'Change settings'; |
30 |
* SettingsTool.prototype.onSelect = function () { |
31 |
* this.setActive( false ); |
32 |
* }; |
33 |
* SettingsTool.prototype.onUpdateState = function () {}; |
34 |
* toolFactory.register( SettingsTool ); |
35 |
* // Register two more tools, nothing interesting here |
36 |
* function StuffTool() { |
37 |
* StuffTool.parent.apply( this, arguments ); |
38 |
* } |
39 |
* OO.inheritClass( StuffTool, OO.ui.Tool ); |
40 |
* StuffTool.static.name = 'stuff'; |
41 |
* StuffTool.static.icon = 'search'; |
42 |
* StuffTool.static.title = 'Change the world'; |
43 |
* StuffTool.prototype.onSelect = function () { |
44 |
* this.setActive( false ); |
45 |
* }; |
46 |
* StuffTool.prototype.onUpdateState = function () {}; |
47 |
* toolFactory.register( StuffTool ); |
48 |
* toolbar.setup( [ |
49 |
* { |
50 |
* // Configurations for list toolgroup. |
51 |
* type: 'list', |
52 |
* label: 'ListToolGroup', |
53 |
* icon: 'ellipsis', |
54 |
* title: 'This is the title, displayed when user moves the mouse over the list toolgroup', |
55 |
* header: 'This is the header', |
56 |
* include: [ 'settings', 'stuff' ], |
57 |
* allowCollapse: ['stuff'] |
58 |
* } |
59 |
* ] ); |
60 |
* |
61 |
* // Create some UI around the toolbar and place it in the document |
62 |
* var frame = new OO.ui.PanelLayout( { |
63 |
* expanded: false, |
64 |
* framed: true |
65 |
* } ); |
66 |
* frame.$element.append( |
67 |
* toolbar.$element |
68 |
* ); |
69 |
* $( 'body' ).append( frame.$element ); |
70 |
* // Build the toolbar. This must be done after the toolbar has been appended to the document. |
71 |
* toolbar.initialize(); |
72 |
* |
73 |
* For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1]. |
74 |
* |
75 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars |
76 |
* |
77 |
* @class |
78 |
* @extends OO.ui.PopupToolGroup |
79 |
* |
80 |
* @constructor |
81 |
* @param {OO.ui.Toolbar} toolbar |
82 |
* @param {Object} [config] Configuration options |
83 |
* @cfg {Array} [allowCollapse] Allow the specified tools to be collapsed. By default, collapsible tools |
84 |
* will only be displayed if users click the ‘More’ option displayed at the bottom of the list. If |
85 |
* the list is expanded, a ‘Fewer’ option permits users to collapse the list again. Any tools that |
86 |
* are included in the toolgroup, but are not designated as collapsible, will always be displayed. |
87 |
* To open a collapsible list in its expanded state, set #expanded to 'true'. |
88 |
* @cfg {Array} [forceExpand] Expand the specified tools. All other tools will be designated as collapsible. |
89 |
* Unless #expanded is set to true, the collapsible tools will be collapsed when the list is first opened. |
90 |
* @cfg {boolean} [expanded=false] Expand collapsible tools. This config is only relevant if tools have |
91 |
* been designated as collapsible. When expanded is set to true, all tools in the group will be displayed |
92 |
* when the list is first opened. Users can collapse the list with a ‘Fewer’ option at the bottom. |
93 |
*/ |
94 |
OO.ui.ListToolGroup = function OoUiListToolGroup( toolbar, config ) { |
95 |
// Allow passing positional parameters inside the config object |
96 |
if ( OO.isPlainObject( toolbar ) && config === undefined ) { |
97 |
config = toolbar; |
98 |
toolbar = config.toolbar; |
99 |
} |
100 |
|
101 |
// Configuration initialization |
102 |
config = config || {}; |
103 |
|
104 |
// Properties (must be set before parent constructor, which calls #populate) |
105 |
this.allowCollapse = config.allowCollapse; |
106 |
this.forceExpand = config.forceExpand; |
107 |
this.expanded = config.expanded !== undefined ? config.expanded : false; |
108 |
this.collapsibleTools = []; |
109 |
|
110 |
// Parent constructor |
111 |
OO.ui.ListToolGroup.parent.call( this, toolbar, config ); |
112 |
|
113 |
// Initialization |
114 |
this.$element.addClass( 'oo-ui-listToolGroup' ); |
115 |
this.$group.addClass( 'oo-ui-listToolGroup-tools' ); |
116 |
}; |
117 |
|
118 |
/* Setup */ |
119 |
|
120 |
OO.inheritClass( OO.ui.ListToolGroup, OO.ui.PopupToolGroup ); |
121 |
|
122 |
/* Static Properties */ |
123 |
|
124 |
/** |
125 |
* @static |
126 |
* @inheritdoc |
127 |
*/ |
128 |
OO.ui.ListToolGroup.static.name = 'list'; |
129 |
|
130 |
/* Methods */ |
131 |
|
132 |
/** |
133 |
* @inheritdoc |
134 |
*/ |
135 |
OO.ui.ListToolGroup.prototype.populate = function () { |
136 |
var i, len, allowCollapse = []; |
137 |
|
138 |
OO.ui.ListToolGroup.parent.prototype.populate.call( this ); |
139 |
|
140 |
// Update the list of collapsible tools |
141 |
if ( this.allowCollapse !== undefined ) { |
142 |
allowCollapse = this.allowCollapse; |
143 |
} else if ( this.forceExpand !== undefined ) { |
144 |
allowCollapse = OO.simpleArrayDifference( Object.keys( this.tools ), this.forceExpand ); |
145 |
} |
146 |
|
147 |
this.collapsibleTools = []; |
148 |
for ( i = 0, len = allowCollapse.length; i < len; i++ ) { |
149 |
if ( this.tools[ allowCollapse[ i ] ] !== undefined ) { |
150 |
this.collapsibleTools.push( this.tools[ allowCollapse[ i ] ] ); |
151 |
} |
152 |
} |
153 |
|
154 |
// Keep at the end, even when tools are added |
155 |
this.$group.append( this.getExpandCollapseTool().$element ); |
156 |
|
157 |
this.getExpandCollapseTool().toggle( this.collapsibleTools.length !== 0 ); |
158 |
this.updateCollapsibleState(); |
159 |
}; |
160 |
|
161 |
/** |
162 |
* Get the expand/collapse tool for this group |
163 |
* |
164 |
* @return {OO.ui.Tool} Expand collapse tool |
165 |
*/ |
166 |
OO.ui.ListToolGroup.prototype.getExpandCollapseTool = function () { |
167 |
var ExpandCollapseTool; |
168 |
if ( this.expandCollapseTool === undefined ) { |
169 |
ExpandCollapseTool = function () { |
170 |
ExpandCollapseTool.parent.apply( this, arguments ); |
171 |
}; |
172 |
|
173 |
OO.inheritClass( ExpandCollapseTool, OO.ui.Tool ); |
174 |
|
175 |
ExpandCollapseTool.prototype.onSelect = function () { |
176 |
this.toolGroup.expanded = !this.toolGroup.expanded; |
177 |
this.toolGroup.updateCollapsibleState(); |
178 |
this.setActive( false ); |
179 |
}; |
180 |
ExpandCollapseTool.prototype.onUpdateState = function () { |
181 |
// Do nothing. Tool interface requires an implementation of this function. |
182 |
}; |
183 |
|
184 |
ExpandCollapseTool.static.name = 'more-fewer'; |
185 |
|
186 |
this.expandCollapseTool = new ExpandCollapseTool( this ); |
187 |
} |
188 |
return this.expandCollapseTool; |
189 |
}; |
190 |
|
191 |
/** |
192 |
* @inheritdoc |
193 |
*/ |
194 |
OO.ui.ListToolGroup.prototype.onMouseKeyUp = function ( e ) { |
195 |
// Do not close the popup when the user wants to show more/fewer tools |
196 |
if ( |
Error |
Row 197, Column 3: "Prefer closest to $.closest"
jquery/no-closest
|
197 |
$( e.target ).closest( '.oo-ui-tool-name-more-fewer' ).length && |
198 |
( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) |
199 |
) { |
200 |
// HACK: Prevent the popup list from being hidden. Skip the PopupToolGroup implementation (which |
201 |
// hides the popup list when a tool is selected) and call ToolGroup's implementation directly. |
202 |
return OO.ui.ListToolGroup.parent.parent.prototype.onMouseKeyUp.call( this, e ); |
203 |
} else { |
204 |
return OO.ui.ListToolGroup.parent.prototype.onMouseKeyUp.call( this, e ); |
205 |
} |
206 |
}; |
207 |
|
208 |
OO.ui.ListToolGroup.prototype.updateCollapsibleState = function () { |
209 |
var i, icon, len; |
210 |
|
211 |
if ( this.toolbar.position !== 'bottom' ) { |
212 |
icon = this.expanded ? 'collapse' : 'expand'; |
213 |
} else { |
214 |
icon = this.expanded ? 'expand' : 'collapse'; |
215 |
} |
216 |
|
217 |
this.getExpandCollapseTool() |
218 |
.setIcon( icon ) |
219 |
.setTitle( OO.ui.msg( this.expanded ? 'ooui-toolgroup-collapse' : 'ooui-toolgroup-expand' ) ); |
220 |
|
221 |
for ( i = 0, len = this.collapsibleTools.length; i < len; i++ ) { |
222 |
this.collapsibleTools[ i ].toggle( this.expanded ); |
223 |
} |
224 |
|
225 |
// Re-evaluate clipping, because our height has changed |
226 |
this.clip(); |
227 |
}; |
228 |
|
|
Line |
Source |
1 |
/** |
2 |
* PopupToolGroup is an abstract base class used by both {@link OO.ui.MenuToolGroup MenuToolGroup} |
3 |
* and {@link OO.ui.ListToolGroup ListToolGroup} to provide a popup (an overlaid menu or list of tools with an |
4 |
* optional icon and label). This class can be used for other base classes that also use this functionality. |
5 |
* |
6 |
* @abstract |
7 |
* @class |
8 |
* @extends OO.ui.ToolGroup |
9 |
* @mixins OO.ui.mixin.IconElement |
10 |
* @mixins OO.ui.mixin.IndicatorElement |
11 |
* @mixins OO.ui.mixin.LabelElement |
12 |
* @mixins OO.ui.mixin.TitledElement |
13 |
* @mixins OO.ui.mixin.FlaggedElement |
14 |
* @mixins OO.ui.mixin.ClippableElement |
15 |
* @mixins OO.ui.mixin.FloatableElement |
16 |
* @mixins OO.ui.mixin.TabIndexedElement |
17 |
* |
18 |
* @constructor |
19 |
* @param {OO.ui.Toolbar} toolbar |
20 |
* @param {Object} [config] Configuration options |
21 |
* @cfg {string} [header] Text to display at the top of the popup |
22 |
*/ |
23 |
OO.ui.PopupToolGroup = function OoUiPopupToolGroup( toolbar, config ) { |
24 |
// Allow passing positional parameters inside the config object |
25 |
if ( OO.isPlainObject( toolbar ) && config === undefined ) { |
26 |
config = toolbar; |
27 |
toolbar = config.toolbar; |
28 |
} |
29 |
|
30 |
// Configuration initialization |
31 |
config = $.extend( { |
32 |
indicator: config.indicator === undefined ? ( toolbar.position === 'bottom' ? 'up' : 'down' ) : config.indicator |
33 |
}, config ); |
34 |
|
35 |
// Parent constructor |
36 |
OO.ui.PopupToolGroup.parent.call( this, toolbar, config ); |
37 |
|
38 |
// Properties |
39 |
this.active = false; |
40 |
this.dragging = false; |
41 |
// Don't conflict with parent method of the same name |
42 |
this.onPopupDocumentMouseKeyUpHandler = this.onPopupDocumentMouseKeyUp.bind( this ); |
43 |
this.$handle = $( '<span>' ); |
44 |
|
45 |
// Mixin constructors |
46 |
OO.ui.mixin.IconElement.call( this, config ); |
47 |
OO.ui.mixin.IndicatorElement.call( this, config ); |
48 |
OO.ui.mixin.LabelElement.call( this, config ); |
49 |
OO.ui.mixin.TitledElement.call( this, config ); |
50 |
OO.ui.mixin.FlaggedElement.call( this, config ); |
51 |
OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) ); |
52 |
OO.ui.mixin.FloatableElement.call( this, $.extend( {}, config, { |
53 |
$floatable: this.$group, |
54 |
$floatableContainer: this.$handle, |
55 |
hideWhenOutOfView: false, |
56 |
verticalPosition: this.toolbar.position === 'bottom' ? 'above' : 'below' |
57 |
} ) ); |
58 |
OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) ); |
59 |
|
60 |
// Events |
61 |
this.$handle.on( { |
62 |
keydown: this.onHandleMouseKeyDown.bind( this ), |
63 |
keyup: this.onHandleMouseKeyUp.bind( this ), |
64 |
mousedown: this.onHandleMouseKeyDown.bind( this ), |
65 |
mouseup: this.onHandleMouseKeyUp.bind( this ) |
66 |
} ); |
67 |
|
68 |
// Initialization |
69 |
this.$handle |
70 |
.addClass( 'oo-ui-popupToolGroup-handle' ) |
71 |
.attr( 'role', 'button' ) |
72 |
.append( this.$icon, this.$label, this.$indicator ); |
73 |
// If the pop-up should have a header, add it to the top of the toolGroup. |
74 |
// Note: If this feature is useful for other widgets, we could abstract it into an |
75 |
// OO.ui.HeaderedElement mixin constructor. |
76 |
if ( config.header !== undefined ) { |
77 |
this.$group |
Error |
Row 78, Column 14: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 78, Column 14: "Prefer classList to $.addClass"
jquery/no-class
|
78 |
.prepend( $( '<span>' ) |
79 |
.addClass( 'oo-ui-popupToolGroup-header' ) |
80 |
.text( config.header ) |
81 |
); |
82 |
} |
83 |
this.$element |
84 |
.addClass( 'oo-ui-popupToolGroup' ) |
85 |
.prepend( this.$handle ); |
86 |
this.$group.addClass( 'oo-ui-popupToolGroup-tools' ); |
87 |
this.toolbar.$popups.append( this.$group ); |
88 |
}; |
89 |
|
90 |
/* Setup */ |
91 |
|
92 |
OO.inheritClass( OO.ui.PopupToolGroup, OO.ui.ToolGroup ); |
93 |
OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.IconElement ); |
94 |
OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.IndicatorElement ); |
95 |
OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.LabelElement ); |
96 |
OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.TitledElement ); |
97 |
OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.FlaggedElement ); |
98 |
OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.ClippableElement ); |
99 |
OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.FloatableElement ); |
100 |
OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.TabIndexedElement ); |
101 |
|
102 |
/* Methods */ |
103 |
|
104 |
/** |
105 |
* @inheritdoc |
106 |
*/ |
107 |
OO.ui.PopupToolGroup.prototype.setDisabled = function () { |
108 |
// Parent method |
109 |
OO.ui.PopupToolGroup.parent.prototype.setDisabled.apply( this, arguments ); |
110 |
|
111 |
if ( this.isDisabled() && this.isElementAttached() ) { |
112 |
this.setActive( false ); |
113 |
} |
114 |
}; |
115 |
|
116 |
/** |
117 |
* Handle document mouse up and key up events. |
118 |
* |
119 |
* @protected |
120 |
* @param {MouseEvent|KeyboardEvent} e Mouse up or key up event |
121 |
*/ |
122 |
OO.ui.PopupToolGroup.prototype.onPopupDocumentMouseKeyUp = function ( e ) { |
123 |
var $target = $( e.target ); |
124 |
// Only deactivate when clicking outside the dropdown element |
Error |
Row 125, Column 7: "Prefer closest to $.closest"
jquery/no-closest
|
125 |
if ( $target.closest( '.oo-ui-popupToolGroup' )[ 0 ] === this.$element[ 0 ] ) { |
126 |
return; |
127 |
} |
Error |
Row 128, Column 7: "Prefer closest to $.closest"
jquery/no-closest
|
128 |
if ( $target.closest( '.oo-ui-popupToolGroup-tools' )[ 0 ] === this.$group[ 0 ] ) { |
129 |
return; |
130 |
} |
131 |
this.setActive( false ); |
132 |
}; |
133 |
|
134 |
// Deprecated alias since 0.28.3 |
135 |
OO.ui.PopupToolGroup.prototype.onBlur = function () { |
136 |
OO.ui.warnDeprecation( 'onBlur is deprecated, use onPopupDocumentMouseKeyUp instead' ); |
137 |
this.onPopupDocumentMouseKeyUp.apply( this, arguments ); |
138 |
}; |
139 |
|
140 |
/** |
141 |
* @inheritdoc |
142 |
*/ |
143 |
OO.ui.PopupToolGroup.prototype.onMouseKeyUp = function ( e ) { |
144 |
// Only close toolgroup when a tool was actually selected |
145 |
if ( |
146 |
!this.isDisabled() && this.pressed && this.pressed === this.findTargetTool( e ) && |
147 |
( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) |
148 |
) { |
149 |
this.setActive( false ); |
150 |
} |
151 |
return OO.ui.PopupToolGroup.parent.prototype.onMouseKeyUp.call( this, e ); |
152 |
}; |
153 |
|
154 |
/** |
155 |
* @inheritdoc |
156 |
*/ |
157 |
OO.ui.PopupToolGroup.prototype.onMouseKeyDown = function ( e ) { |
158 |
var $focused, $firstFocusable, $lastFocusable; |
159 |
// Shift-Tab on the first tool in the group jumps to the handle. |
160 |
// Tab on the last tool in the group jumps to the next group. |
161 |
if ( !this.isDisabled() && e.which === OO.ui.Keys.TAB ) { |
162 |
// (We can't use this.items because ListToolGroup inserts the extra fake expand/collapse tool.) |
163 |
$focused = $( document.activeElement ); |
164 |
$firstFocusable = OO.ui.findFocusable( this.$group ); |
165 |
if ( $focused[ 0 ] === $firstFocusable[ 0 ] && e.shiftKey ) { |
166 |
this.$handle.focus(); |
167 |
return false; |
168 |
} |
169 |
$lastFocusable = OO.ui.findFocusable( this.$group, true ); |
170 |
if ( $focused[ 0 ] === $lastFocusable[ 0 ] && !e.shiftKey ) { |
171 |
// Focus this group's handle and let the browser's tab handling happen (no 'return false'). |
172 |
// This way we don't have to fiddle with other ToolGroups' business, or worry what to do |
173 |
// if the next group is not a PopupToolGroup or doesn't exist at all. |
174 |
this.$handle.focus(); |
175 |
// Close the popup so that we don't move back inside it (if this is the last group). |
176 |
this.setActive( false ); |
177 |
} |
178 |
} |
179 |
return OO.ui.PopupToolGroup.parent.prototype.onMouseKeyDown.call( this, e ); |
180 |
}; |
181 |
|
182 |
/** |
183 |
* Handle mouse up and key up events. |
184 |
* |
185 |
* @protected |
186 |
* @param {jQuery.Event} e Mouse up or key up event |
187 |
*/ |
188 |
OO.ui.PopupToolGroup.prototype.onHandleMouseKeyUp = function ( e ) { |
189 |
if ( |
190 |
!this.isDisabled() && |
191 |
( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) |
192 |
) { |
193 |
return false; |
194 |
} |
195 |
}; |
196 |
|
197 |
/** |
198 |
* Handle mouse down and key down events. |
199 |
* |
200 |
* @protected |
201 |
* @param {jQuery.Event} e Mouse down or key down event |
202 |
*/ |
203 |
OO.ui.PopupToolGroup.prototype.onHandleMouseKeyDown = function ( e ) { |
204 |
var $focusable; |
205 |
if ( !this.isDisabled() ) { |
206 |
// Tab on the handle jumps to the first tool in the group (if the popup is open). |
207 |
if ( e.which === OO.ui.Keys.TAB && !e.shiftKey ) { |
208 |
$focusable = OO.ui.findFocusable( this.$group ); |
209 |
if ( $focusable.length ) { |
210 |
$focusable.focus(); |
211 |
return false; |
212 |
} |
213 |
} |
214 |
if ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) { |
215 |
this.setActive( !this.active ); |
216 |
return false; |
217 |
} |
218 |
} |
219 |
}; |
220 |
|
221 |
/** |
222 |
* Check if the tool group is active. |
223 |
* |
224 |
* @return {boolean} Tool group is active |
225 |
*/ |
226 |
OO.ui.PopupToolGroup.prototype.isActive = function () { |
227 |
return this.active; |
228 |
}; |
229 |
|
230 |
/** |
231 |
* Switch into 'active' mode. |
232 |
* |
233 |
* When active, the popup is visible. A mouseup event anywhere in the document will trigger |
234 |
* deactivation. |
235 |
* |
236 |
* @param {boolean} value The active state to set |
237 |
* @fires active |
238 |
*/ |
239 |
OO.ui.PopupToolGroup.prototype.setActive = function ( value ) { |
240 |
var containerWidth, containerLeft; |
241 |
value = !!value; |
242 |
if ( this.active !== value ) { |
243 |
this.active = value; |
244 |
if ( value ) { |
245 |
this.getElementDocument().addEventListener( 'mouseup', this.onPopupDocumentMouseKeyUpHandler, true ); |
246 |
this.getElementDocument().addEventListener( 'keyup', this.onPopupDocumentMouseKeyUpHandler, true ); |
247 |
|
248 |
this.$clippable.css( 'left', '' ); |
249 |
this.$element.addClass( 'oo-ui-popupToolGroup-active' ); |
250 |
this.$group.addClass( 'oo-ui-popupToolGroup-active-tools' ); |
251 |
this.togglePositioning( true ); |
252 |
this.toggleClipping( true ); |
253 |
|
254 |
// Try anchoring the popup to the left first |
255 |
this.setHorizontalPosition( 'start' ); |
256 |
|
257 |
if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) { |
258 |
// Anchoring to the left caused the popup to clip, so anchor it to the right instead |
259 |
this.setHorizontalPosition( 'end' ); |
260 |
} |
261 |
if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) { |
262 |
// Anchoring to the right also caused the popup to clip, so just make it fill the container |
263 |
containerWidth = this.$clippableScrollableContainer.width(); |
264 |
containerLeft = this.$clippableScrollableContainer[ 0 ] === document.documentElement ? |
265 |
0 : |
266 |
this.$clippableScrollableContainer.offset().left; |
267 |
|
268 |
this.toggleClipping( false ); |
269 |
this.setHorizontalPosition( 'start' ); |
270 |
|
271 |
this.$clippable.css( { |
272 |
'margin-left': -( this.$element.offset().left - containerLeft ), |
273 |
width: containerWidth |
274 |
} ); |
275 |
} |
276 |
} else { |
277 |
this.getElementDocument().removeEventListener( 'mouseup', this.onPopupDocumentMouseKeyUpHandler, true ); |
278 |
this.getElementDocument().removeEventListener( 'keyup', this.onPopupDocumentMouseKeyUpHandler, true ); |
279 |
this.$element.removeClass( 'oo-ui-popupToolGroup-active' ); |
280 |
this.$group.removeClass( 'oo-ui-popupToolGroup-active-tools' ); |
281 |
this.togglePositioning( false ); |
282 |
this.toggleClipping( false ); |
283 |
} |
284 |
this.emit( 'active', this.active ); |
285 |
this.updateThemeClasses(); |
286 |
} |
287 |
}; |
288 |
|
|
|
/src/tools/ToolGroupTool.js
|
0 problems
|
|
/src/Widget.js
|
0 problems
|
|
/src/widgets/ActionWidget.js
|
0 problems
|
|
/src/widgets/ButtonGroupWidget.js
|
0 problems
|
|
/src/widgets/ButtonInputWidget.js
|
0 problems
|
|
/src/widgets/ButtonOptionWidget.js
|
0 problems
|
|
/src/widgets/ButtonSelectWidget.js
|
0 problems
|
|
/src/widgets/ButtonWidget.js
|
0 problems
|
|
/src/widgets/CheckboxInputWidget.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 93, Column 9: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Line |
Source |
1 |
/** |
2 |
* CheckboxInputWidgets, like HTML checkboxes, can be selected and/or configured with a value. |
3 |
* Note that these {@link OO.ui.InputWidget input widgets} are best laid out |
4 |
* in {@link OO.ui.FieldLayout field layouts} that use the {@link OO.ui.FieldLayout#align inline} |
5 |
* alignment. For more information, please see the [OOUI documentation on MediaWiki][1]. |
6 |
* |
7 |
* This widget can be used inside an HTML form, such as a OO.ui.FormLayout. |
8 |
* |
9 |
* @example |
10 |
* // An example of selected, unselected, and disabled checkbox inputs |
11 |
* var checkbox1=new OO.ui.CheckboxInputWidget( { |
12 |
* value: 'a', |
13 |
* selected: true |
14 |
* } ); |
15 |
* var checkbox2=new OO.ui.CheckboxInputWidget( { |
16 |
* value: 'b' |
17 |
* } ); |
18 |
* var checkbox3=new OO.ui.CheckboxInputWidget( { |
19 |
* value:'c', |
20 |
* disabled: true |
21 |
* } ); |
22 |
* // Create a fieldset layout with fields for each checkbox. |
23 |
* var fieldset = new OO.ui.FieldsetLayout( { |
24 |
* label: 'Checkboxes' |
25 |
* } ); |
26 |
* fieldset.addItems( [ |
27 |
* new OO.ui.FieldLayout( checkbox1, { label: 'Selected checkbox', align: 'inline' } ), |
28 |
* new OO.ui.FieldLayout( checkbox2, { label: 'Unselected checkbox', align: 'inline' } ), |
29 |
* new OO.ui.FieldLayout( checkbox3, { label: 'Disabled checkbox', align: 'inline' } ), |
30 |
* ] ); |
31 |
* $( 'body' ).append( fieldset.$element ); |
32 |
* |
33 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs |
34 |
* |
35 |
* @class |
36 |
* @extends OO.ui.InputWidget |
37 |
* |
38 |
* @constructor |
39 |
* @param {Object} [config] Configuration options |
40 |
* @cfg {boolean} [selected=false] Select the checkbox initially. By default, the checkbox is not selected. |
41 |
*/ |
42 |
OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) { |
43 |
// Configuration initialization |
44 |
config = config || {}; |
45 |
|
46 |
// Parent constructor |
47 |
OO.ui.CheckboxInputWidget.parent.call( this, config ); |
48 |
|
49 |
// Properties |
50 |
this.checkIcon = new OO.ui.IconWidget( { |
51 |
icon: 'check', |
52 |
classes: [ 'oo-ui-checkboxInputWidget-checkIcon' ] |
53 |
} ); |
54 |
|
55 |
// Initialization |
56 |
this.$element |
57 |
.addClass( 'oo-ui-checkboxInputWidget' ) |
58 |
// Required for pretty styling in WikimediaUI theme |
59 |
.append( this.checkIcon.$element ); |
60 |
this.setSelected( config.selected !== undefined ? config.selected : false ); |
61 |
}; |
62 |
|
63 |
/* Setup */ |
64 |
|
65 |
OO.inheritClass( OO.ui.CheckboxInputWidget, OO.ui.InputWidget ); |
66 |
|
67 |
/* Static Properties */ |
68 |
|
69 |
/** |
70 |
* @static |
71 |
* @inheritdoc |
72 |
*/ |
73 |
OO.ui.CheckboxInputWidget.static.tagName = 'span'; |
74 |
|
75 |
/* Static Methods */ |
76 |
|
77 |
/** |
78 |
* @inheritdoc |
79 |
*/ |
80 |
OO.ui.CheckboxInputWidget.static.gatherPreInfuseState = function ( node, config ) { |
81 |
var state = OO.ui.CheckboxInputWidget.parent.static.gatherPreInfuseState( node, config ); |
82 |
state.checked = config.$input.prop( 'checked' ); |
83 |
return state; |
84 |
}; |
85 |
|
86 |
/* Methods */ |
87 |
|
88 |
/** |
89 |
* @inheritdoc |
90 |
* @protected |
91 |
*/ |
92 |
OO.ui.CheckboxInputWidget.prototype.getInputElement = function () { |
Error |
Row 93, Column 9: "Prefer setAttribute to $.attr"
jquery/no-attr
|
93 |
return $( '<input>' ).attr( 'type', 'checkbox' ); |
94 |
}; |
95 |
|
96 |
/** |
97 |
* @inheritdoc |
98 |
*/ |
99 |
OO.ui.CheckboxInputWidget.prototype.onEdit = function () { |
100 |
var widget = this; |
101 |
if ( !this.isDisabled() ) { |
102 |
// Allow the stack to clear so the value will be updated |
103 |
setTimeout( function () { |
104 |
widget.setSelected( widget.$input.prop( 'checked' ) ); |
105 |
} ); |
106 |
} |
107 |
}; |
108 |
|
109 |
/** |
110 |
* Set selection state of this checkbox. |
111 |
* |
112 |
* @param {boolean} state `true` for selected |
113 |
* @chainable |
114 |
*/ |
115 |
OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state ) { |
116 |
state = !!state; |
117 |
if ( this.selected !== state ) { |
118 |
this.selected = state; |
119 |
this.$input.prop( 'checked', this.selected ); |
120 |
this.emit( 'change', this.selected ); |
121 |
} |
122 |
// The first time that the selection state is set (probably while constructing the widget), |
123 |
// remember it in defaultSelected. This property can be later used to check whether |
124 |
// the selection state of the input has been changed since it was created. |
125 |
if ( this.defaultSelected === undefined ) { |
126 |
this.defaultSelected = this.selected; |
127 |
this.$input[ 0 ].defaultChecked = this.defaultSelected; |
128 |
} |
129 |
return this; |
130 |
}; |
131 |
|
132 |
/** |
133 |
* Check if this checkbox is selected. |
134 |
* |
135 |
* @return {boolean} Checkbox is selected |
136 |
*/ |
137 |
OO.ui.CheckboxInputWidget.prototype.isSelected = function () { |
138 |
// Resynchronize our internal data with DOM data. Other scripts executing on the page can modify |
139 |
// it, and we won't know unless they're kind enough to trigger a 'change' event. |
140 |
var selected = this.$input.prop( 'checked' ); |
141 |
if ( this.selected !== selected ) { |
142 |
this.setSelected( selected ); |
143 |
} |
144 |
return this.selected; |
145 |
}; |
146 |
|
147 |
/** |
148 |
* @inheritdoc |
149 |
*/ |
150 |
OO.ui.CheckboxInputWidget.prototype.simulateLabelClick = function () { |
151 |
if ( !this.isDisabled() ) { |
152 |
this.$input.click(); |
153 |
} |
154 |
this.focus(); |
155 |
}; |
156 |
|
157 |
/** |
158 |
* @inheritdoc |
159 |
*/ |
160 |
OO.ui.CheckboxInputWidget.prototype.restorePreInfuseState = function ( state ) { |
161 |
OO.ui.CheckboxInputWidget.parent.prototype.restorePreInfuseState.call( this, state ); |
162 |
if ( state.checked !== undefined && state.checked !== this.isSelected() ) { |
163 |
this.setSelected( state.checked ); |
164 |
} |
165 |
}; |
166 |
|
|
|
/src/widgets/CheckboxMultioptionWidget.js
|
0 problems
|
|
/src/widgets/CheckboxMultiselectInputWidget.js
|
2 problems (2 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 65, Column 16: "Prefer Array#map to $.map"
jquery/no-map
|
Error |
Row 65, Column 16: "Prefer querySelectorAll to $.find"
jquery/no-find
|
Line |
Source |
1 |
/** |
2 |
* CheckboxMultiselectInputWidget is a |
3 |
* {@link OO.ui.CheckboxMultiselectWidget CheckboxMultiselectWidget} intended to be used within a |
4 |
* HTML form, such as a OO.ui.FormLayout. The selected values are synchronized with the value of |
5 |
* HTML `<input type=checkbox>` tags. Please see the [OOUI documentation on MediaWiki][1] for |
6 |
* more information about input widgets. |
7 |
* |
8 |
* @example |
9 |
* // Example: A CheckboxMultiselectInputWidget with three options |
10 |
* var multiselectInput = new OO.ui.CheckboxMultiselectInputWidget( { |
11 |
* options: [ |
12 |
* { data: 'a', label: 'First' }, |
13 |
* { data: 'b', label: 'Second'}, |
14 |
* { data: 'c', label: 'Third' } |
15 |
* ] |
16 |
* } ); |
17 |
* $( 'body' ).append( multiselectInput.$element ); |
18 |
* |
19 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs |
20 |
* |
21 |
* @class |
22 |
* @extends OO.ui.InputWidget |
23 |
* |
24 |
* @constructor |
25 |
* @param {Object} [config] Configuration options |
26 |
* @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: …, disabled: … }` |
27 |
*/ |
28 |
OO.ui.CheckboxMultiselectInputWidget = function OoUiCheckboxMultiselectInputWidget( config ) { |
29 |
// Configuration initialization |
30 |
config = config || {}; |
31 |
|
32 |
// Properties (must be done before parent constructor which calls #setDisabled) |
33 |
this.checkboxMultiselectWidget = new OO.ui.CheckboxMultiselectWidget(); |
34 |
// Must be set before the #setOptionsData call below |
35 |
this.inputName = config.name; |
36 |
// Set up the options before parent constructor, which uses them to validate config.value. |
37 |
// Use this instead of setOptions() because this.$input is not set up yet |
38 |
this.setOptionsData( config.options || [] ); |
39 |
|
40 |
// Parent constructor |
41 |
OO.ui.CheckboxMultiselectInputWidget.parent.call( this, config ); |
42 |
|
43 |
// Events |
44 |
this.checkboxMultiselectWidget.connect( this, { select: 'onCheckboxesSelect' } ); |
45 |
|
46 |
// Initialization |
47 |
this.$element |
48 |
.addClass( 'oo-ui-checkboxMultiselectInputWidget' ) |
49 |
.append( this.checkboxMultiselectWidget.$element ); |
50 |
// We don't use this.$input, but rather the CheckboxInputWidgets inside each option |
51 |
this.$input.detach(); |
52 |
}; |
53 |
|
54 |
/* Setup */ |
55 |
|
56 |
OO.inheritClass( OO.ui.CheckboxMultiselectInputWidget, OO.ui.InputWidget ); |
57 |
|
58 |
/* Static Methods */ |
59 |
|
60 |
/** |
61 |
* @inheritdoc |
62 |
*/ |
63 |
OO.ui.CheckboxMultiselectInputWidget.static.gatherPreInfuseState = function ( node, config ) { |
64 |
var state = OO.ui.CheckboxMultiselectInputWidget.parent.static.gatherPreInfuseState( node, config ); |
Error |
Row 65, Column 16: "Prefer Array#map to $.map"
jquery/no-map
|
Error |
Row 65, Column 16: "Prefer querySelectorAll to $.find"
jquery/no-find
|
65 |
state.value = $( node ).find( '.oo-ui-checkboxInputWidget .oo-ui-inputWidget-input:checked' ) |
66 |
.toArray().map( function ( el ) { return el.value; } ); |
67 |
return state; |
68 |
}; |
69 |
|
70 |
/** |
71 |
* @inheritdoc |
72 |
*/ |
73 |
OO.ui.CheckboxMultiselectInputWidget.static.reusePreInfuseDOM = function ( node, config ) { |
74 |
config = OO.ui.CheckboxMultiselectInputWidget.parent.static.reusePreInfuseDOM( node, config ); |
75 |
// Cannot reuse the `<input type=checkbox>` set |
76 |
delete config.$input; |
77 |
return config; |
78 |
}; |
79 |
|
80 |
/* Methods */ |
81 |
|
82 |
/** |
83 |
* @inheritdoc |
84 |
* @protected |
85 |
*/ |
86 |
OO.ui.CheckboxMultiselectInputWidget.prototype.getInputElement = function () { |
87 |
// Actually unused |
88 |
return $( '<unused>' ); |
89 |
}; |
90 |
|
91 |
/** |
92 |
* Handles CheckboxMultiselectWidget select events. |
93 |
* |
94 |
* @private |
95 |
*/ |
96 |
OO.ui.CheckboxMultiselectInputWidget.prototype.onCheckboxesSelect = function () { |
97 |
this.setValue( this.checkboxMultiselectWidget.findSelectedItemsData() ); |
98 |
}; |
99 |
|
100 |
/** |
101 |
* @inheritdoc |
102 |
*/ |
103 |
OO.ui.CheckboxMultiselectInputWidget.prototype.getValue = function () { |
104 |
var value = this.$element.find( '.oo-ui-checkboxInputWidget .oo-ui-inputWidget-input:checked' ) |
105 |
.toArray().map( function ( el ) { return el.value; } ); |
106 |
if ( this.value !== value ) { |
107 |
this.setValue( value ); |
108 |
} |
109 |
return this.value; |
110 |
}; |
111 |
|
112 |
/** |
113 |
* @inheritdoc |
114 |
*/ |
115 |
OO.ui.CheckboxMultiselectInputWidget.prototype.setValue = function ( value ) { |
116 |
value = this.cleanUpValue( value ); |
117 |
this.checkboxMultiselectWidget.selectItemsByData( value ); |
118 |
OO.ui.CheckboxMultiselectInputWidget.parent.prototype.setValue.call( this, value ); |
119 |
if ( this.optionsDirty ) { |
120 |
// We reached this from the constructor or from #setOptions. |
121 |
// We have to update the <select> element. |
122 |
this.updateOptionsInterface(); |
123 |
} |
124 |
return this; |
125 |
}; |
126 |
|
127 |
/** |
128 |
* Clean up incoming value. |
129 |
* |
130 |
* @param {string[]} value Original value |
131 |
* @return {string[]} Cleaned up value |
132 |
*/ |
133 |
OO.ui.CheckboxMultiselectInputWidget.prototype.cleanUpValue = function ( value ) { |
134 |
var i, singleValue, |
135 |
cleanValue = []; |
136 |
if ( !Array.isArray( value ) ) { |
137 |
return cleanValue; |
138 |
} |
139 |
for ( i = 0; i < value.length; i++ ) { |
140 |
singleValue = |
141 |
OO.ui.CheckboxMultiselectInputWidget.parent.prototype.cleanUpValue.call( this, value[ i ] ); |
142 |
// Remove options that we don't have here |
143 |
if ( !this.checkboxMultiselectWidget.findItemFromData( singleValue ) ) { |
144 |
continue; |
145 |
} |
146 |
cleanValue.push( singleValue ); |
147 |
} |
148 |
return cleanValue; |
149 |
}; |
150 |
|
151 |
/** |
152 |
* @inheritdoc |
153 |
*/ |
154 |
OO.ui.CheckboxMultiselectInputWidget.prototype.setDisabled = function ( state ) { |
155 |
this.checkboxMultiselectWidget.setDisabled( state ); |
156 |
OO.ui.CheckboxMultiselectInputWidget.parent.prototype.setDisabled.call( this, state ); |
157 |
return this; |
158 |
}; |
159 |
|
160 |
/** |
161 |
* Set the options available for this input. |
162 |
* |
163 |
* @param {Object[]} options Array of menu options in the format `{ data: …, label: …, disabled: … }` |
164 |
* @chainable |
165 |
*/ |
166 |
OO.ui.CheckboxMultiselectInputWidget.prototype.setOptions = function ( options ) { |
167 |
var value = this.getValue(); |
168 |
|
169 |
this.setOptionsData( options ); |
170 |
|
171 |
// Re-set the value to update the visible interface (CheckboxMultiselectWidget). |
172 |
// This will also get rid of any stale options that we just removed. |
173 |
this.setValue( value ); |
174 |
|
175 |
return this; |
176 |
}; |
177 |
|
178 |
/** |
179 |
* Set the internal list of options, used e.g. by setValue() to see which options are allowed. |
180 |
* |
181 |
* This method may be called before the parent constructor, so various properties may not be |
182 |
* intialized yet. |
183 |
* |
184 |
* @param {Object[]} options Array of menu options in the format `{ data: …, label: … }` |
185 |
* @private |
186 |
*/ |
187 |
OO.ui.CheckboxMultiselectInputWidget.prototype.setOptionsData = function ( options ) { |
188 |
var widget = this; |
189 |
|
190 |
this.optionsDirty = true; |
191 |
|
192 |
this.checkboxMultiselectWidget |
193 |
.clearItems() |
194 |
.addItems( options.map( function ( opt ) { |
195 |
var optValue, item, optDisabled; |
196 |
optValue = |
197 |
OO.ui.CheckboxMultiselectInputWidget.parent.prototype.cleanUpValue.call( widget, opt.data ); |
198 |
optDisabled = opt.disabled !== undefined ? opt.disabled : false; |
199 |
item = new OO.ui.CheckboxMultioptionWidget( { |
200 |
data: optValue, |
201 |
label: opt.label !== undefined ? opt.label : optValue, |
202 |
disabled: optDisabled |
203 |
} ); |
204 |
// Set the 'name' and 'value' for form submission |
205 |
item.checkbox.$input.attr( 'name', widget.inputName ); |
206 |
item.checkbox.setValue( optValue ); |
207 |
return item; |
208 |
} ) ); |
209 |
}; |
210 |
|
211 |
/** |
212 |
* Update the user-visible interface to match the internal list of options and value. |
213 |
* |
214 |
* This method must only be called after the parent constructor. |
215 |
* |
216 |
* @private |
217 |
*/ |
218 |
OO.ui.CheckboxMultiselectInputWidget.prototype.updateOptionsInterface = function () { |
219 |
var defaultValue = this.defaultValue; |
220 |
|
221 |
this.checkboxMultiselectWidget.getItems().forEach( function ( item ) { |
222 |
// Remember original selection state. This property can be later used to check whether |
223 |
// the selection state of the input has been changed since it was created. |
224 |
var isDefault = defaultValue.indexOf( item.getData() ) !== -1; |
225 |
item.checkbox.defaultSelected = isDefault; |
226 |
item.checkbox.$input[ 0 ].defaultChecked = isDefault; |
227 |
} ); |
228 |
|
229 |
this.optionsDirty = false; |
230 |
}; |
231 |
|
232 |
/** |
233 |
* @inheritdoc |
234 |
*/ |
235 |
OO.ui.CheckboxMultiselectInputWidget.prototype.focus = function () { |
236 |
this.checkboxMultiselectWidget.focus(); |
237 |
return this; |
238 |
}; |
239 |
|
|
|
/src/widgets/CheckboxMultiselectWidget.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 100, Column 17: "Prefer closest to $.closest"
jquery/no-closest
|
Line |
Source |
1 |
/** |
2 |
* CheckboxMultiselectWidget is a {@link OO.ui.MultiselectWidget multiselect widget} that contains |
3 |
* checkboxes and is used together with OO.ui.CheckboxMultioptionWidget. The |
4 |
* CheckboxMultiselectWidget provides an interface for adding, removing and selecting options. |
5 |
* Please see the [OOUI documentation on MediaWiki][1] for more information. |
6 |
* |
7 |
* If you want to use this within an HTML form, such as a OO.ui.FormLayout, use |
8 |
* OO.ui.CheckboxMultiselectInputWidget instead. |
9 |
* |
10 |
* @example |
11 |
* // A CheckboxMultiselectWidget with CheckboxMultioptions. |
12 |
* var option1 = new OO.ui.CheckboxMultioptionWidget( { |
13 |
* data: 'a', |
14 |
* selected: true, |
15 |
* label: 'Selected checkbox' |
16 |
* } ); |
17 |
* |
18 |
* var option2 = new OO.ui.CheckboxMultioptionWidget( { |
19 |
* data: 'b', |
20 |
* label: 'Unselected checkbox' |
21 |
* } ); |
22 |
* |
23 |
* var multiselect=new OO.ui.CheckboxMultiselectWidget( { |
24 |
* items: [ option1, option2 ] |
25 |
* } ); |
26 |
* |
27 |
* $( 'body' ).append( multiselect.$element ); |
28 |
* |
29 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options |
30 |
* |
31 |
* @class |
32 |
* @extends OO.ui.MultiselectWidget |
33 |
* |
34 |
* @constructor |
35 |
* @param {Object} [config] Configuration options |
36 |
*/ |
37 |
OO.ui.CheckboxMultiselectWidget = function OoUiCheckboxMultiselectWidget( config ) { |
38 |
// Parent constructor |
39 |
OO.ui.CheckboxMultiselectWidget.parent.call( this, config ); |
40 |
|
41 |
// Properties |
42 |
this.$lastClicked = null; |
43 |
|
44 |
// Events |
45 |
this.$group.on( 'click', this.onClick.bind( this ) ); |
46 |
|
47 |
// Initialization |
48 |
this.$element |
49 |
.addClass( 'oo-ui-checkboxMultiselectWidget' ); |
50 |
}; |
51 |
|
52 |
/* Setup */ |
53 |
|
54 |
OO.inheritClass( OO.ui.CheckboxMultiselectWidget, OO.ui.MultiselectWidget ); |
55 |
|
56 |
/* Methods */ |
57 |
|
58 |
/** |
59 |
* Get an option by its position relative to the specified item (or to the start of the option array, |
60 |
* if item is `null`). The direction in which to search through the option array is specified with a |
61 |
* number: -1 for reverse (the default) or 1 for forward. The method will return an option, or |
62 |
* `null` if there are no options in the array. |
63 |
* |
64 |
* @param {OO.ui.CheckboxMultioptionWidget|null} item Item to describe the start position, or `null` to start at the beginning of the array. |
65 |
* @param {number} direction Direction to move in: -1 to move backward, 1 to move forward |
66 |
* @return {OO.ui.CheckboxMultioptionWidget|null} Item at position, `null` if there are no items in the select |
67 |
*/ |
68 |
OO.ui.CheckboxMultiselectWidget.prototype.getRelativeFocusableItem = function ( item, direction ) { |
69 |
var currentIndex, nextIndex, i, |
70 |
increase = direction > 0 ? 1 : -1, |
71 |
len = this.items.length; |
72 |
|
73 |
if ( item ) { |
74 |
currentIndex = this.items.indexOf( item ); |
75 |
nextIndex = ( currentIndex + increase + len ) % len; |
76 |
} else { |
77 |
// If no item is selected and moving forward, start at the beginning. |
78 |
// If moving backward, start at the end. |
79 |
nextIndex = direction > 0 ? 0 : len - 1; |
80 |
} |
81 |
|
82 |
for ( i = 0; i < len; i++ ) { |
83 |
item = this.items[ nextIndex ]; |
84 |
if ( item && !item.isDisabled() ) { |
85 |
return item; |
86 |
} |
87 |
nextIndex = ( nextIndex + increase + len ) % len; |
88 |
} |
89 |
return null; |
90 |
}; |
91 |
|
92 |
/** |
93 |
* Handle click events on checkboxes. |
94 |
* |
95 |
* @param {jQuery.Event} e |
96 |
*/ |
97 |
OO.ui.CheckboxMultiselectWidget.prototype.onClick = function ( e ) { |
98 |
var $options, lastClickedIndex, nowClickedIndex, i, direction, wasSelected, items, |
99 |
$lastClicked = this.$lastClicked, |
Error |
Row 100, Column 17: "Prefer closest to $.closest"
jquery/no-closest
|
100 |
$nowClicked = $( e.target ).closest( '.oo-ui-checkboxMultioptionWidget' ) |
101 |
.not( '.oo-ui-widget-disabled' ); |
102 |
|
103 |
// Allow selecting multiple options at once by Shift-clicking them |
104 |
if ( $lastClicked && $nowClicked.length && e.shiftKey ) { |
105 |
$options = this.$group.find( '.oo-ui-checkboxMultioptionWidget' ); |
106 |
lastClickedIndex = $options.index( $lastClicked ); |
107 |
nowClickedIndex = $options.index( $nowClicked ); |
108 |
// If it's the same item, either the user is being silly, or it's a fake event generated by the |
109 |
// browser. In either case we don't need custom handling. |
110 |
if ( nowClickedIndex !== lastClickedIndex ) { |
111 |
items = this.items; |
112 |
wasSelected = items[ nowClickedIndex ].isSelected(); |
113 |
direction = nowClickedIndex > lastClickedIndex ? 1 : -1; |
114 |
|
115 |
// This depends on the DOM order of the items and the order of the .items array being the same. |
116 |
for ( i = lastClickedIndex; i !== nowClickedIndex; i += direction ) { |
117 |
if ( !items[ i ].isDisabled() ) { |
118 |
items[ i ].setSelected( !wasSelected ); |
119 |
} |
120 |
} |
121 |
// For the now-clicked element, use immediate timeout to allow the browser to do its own |
122 |
// handling first, then set our value. The order in which events happen is different for |
123 |
// clicks on the <input> and on the <label> and there are additional fake clicks fired for |
124 |
// non-click actions that change the checkboxes. |
125 |
e.preventDefault(); |
126 |
setTimeout( function () { |
127 |
if ( !items[ nowClickedIndex ].isDisabled() ) { |
128 |
items[ nowClickedIndex ].setSelected( !wasSelected ); |
129 |
} |
130 |
} ); |
131 |
} |
132 |
} |
133 |
|
134 |
if ( $nowClicked.length ) { |
135 |
this.$lastClicked = $nowClicked; |
136 |
} |
137 |
}; |
138 |
|
139 |
/** |
140 |
* Focus the widget |
141 |
* |
142 |
* @chainable |
143 |
*/ |
144 |
OO.ui.CheckboxMultiselectWidget.prototype.focus = function () { |
145 |
var item; |
146 |
if ( !this.isDisabled() ) { |
147 |
item = this.getRelativeFocusableItem( null, 1 ); |
148 |
if ( item ) { |
149 |
item.focus(); |
150 |
} |
151 |
} |
152 |
return this; |
153 |
}; |
154 |
|
155 |
/** |
156 |
* @inheritdoc |
157 |
*/ |
158 |
OO.ui.CheckboxMultiselectWidget.prototype.simulateLabelClick = function () { |
159 |
this.focus(); |
160 |
}; |
161 |
|
|
|
/src/widgets/ComboBoxInputWidget.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 125, Column 16: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* ComboBoxInputWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value |
3 |
* can be entered manually) and a {@link OO.ui.MenuSelectWidget menu of options} (from which |
4 |
* a value can be chosen instead). Users can choose options from the combo box in one of two ways: |
5 |
* |
6 |
* - by typing a value in the text input field. If the value exactly matches the value of a menu |
7 |
* option, that option will appear to be selected. |
8 |
* - by choosing a value from the menu. The value of the chosen option will then appear in the text |
9 |
* input field. |
10 |
* |
11 |
* After the user chooses an option, its `data` will be used as a new value for the widget. |
12 |
* A `label` also can be specified for each option: if given, it will be shown instead of the |
13 |
* `data` in the dropdown menu. |
14 |
* |
15 |
* This widget can be used inside an HTML form, such as a OO.ui.FormLayout. |
16 |
* |
17 |
* For more information about menus and options, please see the [OOUI documentation on MediaWiki][1]. |
18 |
* |
19 |
* @example |
20 |
* // Example: A ComboBoxInputWidget. |
21 |
* var comboBox = new OO.ui.ComboBoxInputWidget( { |
22 |
* value: 'Option 1', |
23 |
* options: [ |
24 |
* { data: 'Option 1' }, |
25 |
* { data: 'Option 2' }, |
26 |
* { data: 'Option 3' } |
27 |
* ] |
28 |
* } ); |
29 |
* $( 'body' ).append( comboBox.$element ); |
30 |
* |
31 |
* @example |
32 |
* // Example: A ComboBoxInputWidget with additional option labels. |
33 |
* var comboBox = new OO.ui.ComboBoxInputWidget( { |
34 |
* value: 'Option 1', |
35 |
* options: [ |
36 |
* { |
37 |
* data: 'Option 1', |
38 |
* label: 'Option One' |
39 |
* }, |
40 |
* { |
41 |
* data: 'Option 2', |
42 |
* label: 'Option Two' |
43 |
* }, |
44 |
* { |
45 |
* data: 'Option 3', |
46 |
* label: 'Option Three' |
47 |
* } |
48 |
* ] |
49 |
* } ); |
50 |
* $( 'body' ).append( comboBox.$element ); |
51 |
* |
52 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options#Menu_selects_and_options |
53 |
* |
54 |
* @class |
55 |
* @extends OO.ui.TextInputWidget |
56 |
* |
57 |
* @constructor |
58 |
* @param {Object} [config] Configuration options |
59 |
* @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: … }` |
60 |
* @cfg {Object} [menu] Configuration options to pass to the {@link OO.ui.MenuSelectWidget menu select widget}. |
61 |
* @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where |
62 |
* the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the |
63 |
* containing `<div>` and has a larger area. By default, the menu uses relative positioning. |
64 |
* See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>. |
65 |
*/ |
66 |
OO.ui.ComboBoxInputWidget = function OoUiComboBoxInputWidget( config ) { |
67 |
// Configuration initialization |
68 |
config = $.extend( { |
69 |
autocomplete: false |
70 |
}, config ); |
71 |
|
72 |
// ComboBoxInputWidget shouldn't support `multiline` |
73 |
config.multiline = false; |
74 |
|
75 |
// See InputWidget#reusePreInfuseDOM about `config.$input` |
76 |
if ( config.$input ) { |
77 |
config.$input.removeAttr( 'list' ); |
78 |
} |
79 |
|
80 |
// Parent constructor |
81 |
OO.ui.ComboBoxInputWidget.parent.call( this, config ); |
82 |
|
83 |
// Properties |
84 |
this.$overlay = ( config.$overlay === true ? OO.ui.getDefaultOverlay() : config.$overlay ) || this.$element; |
85 |
this.dropdownButton = new OO.ui.ButtonWidget( { |
86 |
classes: [ 'oo-ui-comboBoxInputWidget-dropdownButton' ], |
87 |
indicator: 'down', |
88 |
disabled: this.disabled |
89 |
} ); |
90 |
this.menu = new OO.ui.MenuSelectWidget( $.extend( |
91 |
{ |
92 |
widget: this, |
93 |
input: this, |
94 |
$floatableContainer: this.$element, |
95 |
disabled: this.isDisabled() |
96 |
}, |
97 |
config.menu |
98 |
) ); |
99 |
|
100 |
// Events |
101 |
this.connect( this, { |
102 |
change: 'onInputChange', |
103 |
enter: 'onInputEnter' |
104 |
} ); |
105 |
this.dropdownButton.connect( this, { |
106 |
click: 'onDropdownButtonClick' |
107 |
} ); |
108 |
this.menu.connect( this, { |
109 |
choose: 'onMenuChoose', |
110 |
add: 'onMenuItemsChange', |
111 |
remove: 'onMenuItemsChange', |
112 |
toggle: 'onMenuToggle' |
113 |
} ); |
114 |
|
115 |
// Initialization |
116 |
this.$input.attr( { |
117 |
role: 'combobox', |
118 |
'aria-owns': this.menu.getElementId(), |
119 |
'aria-autocomplete': 'list' |
120 |
} ); |
121 |
// Do not override options set via config.menu.items |
122 |
if ( config.options !== undefined ) { |
123 |
this.setOptions( config.options ); |
124 |
} |
Error |
Row 125, Column 16: "Prefer classList to $.addClass"
jquery/no-class
|
125 |
this.$field = $( '<div>' ) |
126 |
.addClass( 'oo-ui-comboBoxInputWidget-field' ) |
127 |
.append( this.$input, this.dropdownButton.$element ); |
128 |
this.$element |
129 |
.addClass( 'oo-ui-comboBoxInputWidget' ) |
130 |
.append( this.$field ); |
131 |
this.$overlay.append( this.menu.$element ); |
132 |
this.onMenuItemsChange(); |
133 |
}; |
134 |
|
135 |
/* Setup */ |
136 |
|
137 |
OO.inheritClass( OO.ui.ComboBoxInputWidget, OO.ui.TextInputWidget ); |
138 |
|
139 |
/* Methods */ |
140 |
|
141 |
/** |
142 |
* Get the combobox's menu. |
143 |
* |
144 |
* @return {OO.ui.MenuSelectWidget} Menu widget |
145 |
*/ |
146 |
OO.ui.ComboBoxInputWidget.prototype.getMenu = function () { |
147 |
return this.menu; |
148 |
}; |
149 |
|
150 |
/** |
151 |
* Get the combobox's text input widget. |
152 |
* |
153 |
* @return {OO.ui.TextInputWidget} Text input widget |
154 |
*/ |
155 |
OO.ui.ComboBoxInputWidget.prototype.getInput = function () { |
156 |
return this; |
157 |
}; |
158 |
|
159 |
/** |
160 |
* Handle input change events. |
161 |
* |
162 |
* @private |
163 |
* @param {string} value New value |
164 |
*/ |
165 |
OO.ui.ComboBoxInputWidget.prototype.onInputChange = function ( value ) { |
166 |
var match = this.menu.findItemFromData( value ); |
167 |
|
168 |
this.menu.selectItem( match ); |
169 |
if ( this.menu.findHighlightedItem() ) { |
170 |
this.menu.highlightItem( match ); |
171 |
} |
172 |
|
173 |
if ( !this.isDisabled() ) { |
174 |
this.menu.toggle( true ); |
175 |
} |
176 |
}; |
177 |
|
178 |
/** |
179 |
* Handle input enter events. |
180 |
* |
181 |
* @private |
182 |
*/ |
183 |
OO.ui.ComboBoxInputWidget.prototype.onInputEnter = function () { |
184 |
if ( !this.isDisabled() ) { |
185 |
this.menu.toggle( false ); |
186 |
} |
187 |
}; |
188 |
|
189 |
/** |
190 |
* Handle button click events. |
191 |
* |
192 |
* @private |
193 |
*/ |
194 |
OO.ui.ComboBoxInputWidget.prototype.onDropdownButtonClick = function () { |
195 |
this.menu.toggle(); |
196 |
this.focus(); |
197 |
}; |
198 |
|
199 |
/** |
200 |
* Handle menu choose events. |
201 |
* |
202 |
* @private |
203 |
* @param {OO.ui.OptionWidget} item Chosen item |
204 |
*/ |
205 |
OO.ui.ComboBoxInputWidget.prototype.onMenuChoose = function ( item ) { |
206 |
this.setValue( item.getData() ); |
207 |
}; |
208 |
|
209 |
/** |
210 |
* Handle menu item change events. |
211 |
* |
212 |
* @private |
213 |
*/ |
214 |
OO.ui.ComboBoxInputWidget.prototype.onMenuItemsChange = function () { |
215 |
var match = this.menu.findItemFromData( this.getValue() ); |
216 |
this.menu.selectItem( match ); |
217 |
if ( this.menu.findHighlightedItem() ) { |
218 |
this.menu.highlightItem( match ); |
219 |
} |
220 |
this.$element.toggleClass( 'oo-ui-comboBoxInputWidget-empty', this.menu.isEmpty() ); |
221 |
}; |
222 |
|
223 |
/** |
224 |
* Handle menu toggle events. |
225 |
* |
226 |
* @private |
227 |
* @param {boolean} isVisible Open state of the menu |
228 |
*/ |
229 |
OO.ui.ComboBoxInputWidget.prototype.onMenuToggle = function ( isVisible ) { |
230 |
this.$element.toggleClass( 'oo-ui-comboBoxInputWidget-open', isVisible ); |
231 |
}; |
232 |
|
233 |
/** |
234 |
* @inheritdoc |
235 |
*/ |
236 |
OO.ui.ComboBoxInputWidget.prototype.setDisabled = function ( disabled ) { |
237 |
// Parent method |
238 |
OO.ui.ComboBoxInputWidget.parent.prototype.setDisabled.call( this, disabled ); |
239 |
|
240 |
if ( this.dropdownButton ) { |
241 |
this.dropdownButton.setDisabled( this.isDisabled() ); |
242 |
} |
243 |
if ( this.menu ) { |
244 |
this.menu.setDisabled( this.isDisabled() ); |
245 |
} |
246 |
|
247 |
return this; |
248 |
}; |
249 |
|
250 |
/** |
251 |
* Set the options available for this input. |
252 |
* |
253 |
* @param {Object[]} options Array of menu options in the format `{ data: …, label: … }` |
254 |
* @chainable |
255 |
*/ |
256 |
OO.ui.ComboBoxInputWidget.prototype.setOptions = function ( options ) { |
257 |
this.getMenu() |
258 |
.clearItems() |
259 |
.addItems( options.map( function ( opt ) { |
260 |
return new OO.ui.MenuOptionWidget( { |
261 |
data: opt.data, |
262 |
label: opt.label !== undefined ? opt.label : opt.data |
263 |
} ); |
264 |
} ) ); |
265 |
|
266 |
return this; |
267 |
}; |
268 |
|
|
|
/src/widgets/DecoratedOptionWidget.js
|
0 problems
|
|
/src/widgets/DropdownInputWidget.js
|
3 problems (3 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 218, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 218, Column 18: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 228, Column 18: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Line |
Source |
1 |
/** |
2 |
* DropdownInputWidget is a {@link OO.ui.DropdownWidget DropdownWidget} intended to be used |
3 |
* within an HTML form, such as a OO.ui.FormLayout. The selected value is synchronized with the value |
4 |
* of a hidden HTML `input` tag. Please see the [OOUI documentation on MediaWiki][1] for |
5 |
* more information about input widgets. |
6 |
* |
7 |
* A DropdownInputWidget always has a value (one of the options is always selected), unless there |
8 |
* are no options. If no `value` configuration option is provided, the first option is selected. |
9 |
* If you need a state representing no value (no option being selected), use a DropdownWidget. |
10 |
* |
11 |
* This and OO.ui.RadioSelectInputWidget support the same configuration options. |
12 |
* |
13 |
* @example |
14 |
* // Example: A DropdownInputWidget with three options |
15 |
* var dropdownInput = new OO.ui.DropdownInputWidget( { |
16 |
* options: [ |
17 |
* { data: 'a', label: 'First' }, |
18 |
* { data: 'b', label: 'Second'}, |
19 |
* { data: 'c', label: 'Third' } |
20 |
* ] |
21 |
* } ); |
22 |
* $( 'body' ).append( dropdownInput.$element ); |
23 |
* |
24 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs |
25 |
* |
26 |
* @class |
27 |
* @extends OO.ui.InputWidget |
28 |
* |
29 |
* @constructor |
30 |
* @param {Object} [config] Configuration options |
31 |
* @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: … }` |
32 |
* @cfg {Object} [dropdown] Configuration options for {@link OO.ui.DropdownWidget DropdownWidget} |
33 |
* @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where |
34 |
* the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the |
35 |
* containing `<div>` and has a larger area. By default, the menu uses relative positioning. |
36 |
* See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>. |
37 |
*/ |
38 |
OO.ui.DropdownInputWidget = function OoUiDropdownInputWidget( config ) { |
39 |
// Configuration initialization |
40 |
config = config || {}; |
41 |
|
42 |
// Properties (must be done before parent constructor which calls #setDisabled) |
43 |
this.dropdownWidget = new OO.ui.DropdownWidget( $.extend( |
44 |
{ |
45 |
$overlay: config.$overlay |
46 |
}, |
47 |
config.dropdown |
48 |
) ); |
49 |
// Set up the options before parent constructor, which uses them to validate config.value. |
50 |
// Use this instead of setOptions() because this.$input is not set up yet. |
51 |
this.setOptionsData( config.options || [] ); |
52 |
|
53 |
// Parent constructor |
54 |
OO.ui.DropdownInputWidget.parent.call( this, config ); |
55 |
|
56 |
// Events |
57 |
this.dropdownWidget.getMenu().connect( this, { select: 'onMenuSelect' } ); |
58 |
|
59 |
// Initialization |
60 |
this.$element |
61 |
.addClass( 'oo-ui-dropdownInputWidget' ) |
62 |
.append( this.dropdownWidget.$element ); |
63 |
this.setTabIndexedElement( this.dropdownWidget.$tabIndexed ); |
64 |
}; |
65 |
|
66 |
/* Setup */ |
67 |
|
68 |
OO.inheritClass( OO.ui.DropdownInputWidget, OO.ui.InputWidget ); |
69 |
|
70 |
/* Methods */ |
71 |
|
72 |
/** |
73 |
* @inheritdoc |
74 |
* @protected |
75 |
*/ |
76 |
OO.ui.DropdownInputWidget.prototype.getInputElement = function () { |
77 |
return $( '<select>' ); |
78 |
}; |
79 |
|
80 |
/** |
81 |
* Handles menu select events. |
82 |
* |
83 |
* @private |
84 |
* @param {OO.ui.MenuOptionWidget|null} item Selected menu item |
85 |
*/ |
86 |
OO.ui.DropdownInputWidget.prototype.onMenuSelect = function ( item ) { |
87 |
this.setValue( item ? item.getData() : '' ); |
88 |
}; |
89 |
|
90 |
/** |
91 |
* @inheritdoc |
92 |
*/ |
93 |
OO.ui.DropdownInputWidget.prototype.setValue = function ( value ) { |
94 |
var selected; |
95 |
value = this.cleanUpValue( value ); |
96 |
// Only allow setting values that are actually present in the dropdown |
97 |
selected = this.dropdownWidget.getMenu().findItemFromData( value ) || |
98 |
this.dropdownWidget.getMenu().findFirstSelectableItem(); |
99 |
this.dropdownWidget.getMenu().selectItem( selected ); |
100 |
value = selected ? selected.getData() : ''; |
101 |
OO.ui.DropdownInputWidget.parent.prototype.setValue.call( this, value ); |
102 |
if ( this.optionsDirty ) { |
103 |
// We reached this from the constructor or from #setOptions. |
104 |
// We have to update the <select> element. |
105 |
this.updateOptionsInterface(); |
106 |
} |
107 |
return this; |
108 |
}; |
109 |
|
110 |
/** |
111 |
* @inheritdoc |
112 |
*/ |
113 |
OO.ui.DropdownInputWidget.prototype.setDisabled = function ( state ) { |
114 |
this.dropdownWidget.setDisabled( state ); |
115 |
OO.ui.DropdownInputWidget.parent.prototype.setDisabled.call( this, state ); |
116 |
return this; |
117 |
}; |
118 |
|
119 |
/** |
120 |
* Set the options available for this input. |
121 |
* |
122 |
* @param {Object[]} options Array of menu options in the format `{ data: …, label: … }` |
123 |
* @chainable |
124 |
*/ |
125 |
OO.ui.DropdownInputWidget.prototype.setOptions = function ( options ) { |
126 |
var value = this.getValue(); |
127 |
|
128 |
this.setOptionsData( options ); |
129 |
|
130 |
// Re-set the value to update the visible interface (DropdownWidget and <select>). |
131 |
// In case the previous value is no longer an available option, select the first valid one. |
132 |
this.setValue( value ); |
133 |
|
134 |
return this; |
135 |
}; |
136 |
|
137 |
/** |
138 |
* Set the internal list of options, used e.g. by setValue() to see which options are allowed. |
139 |
* |
140 |
* This method may be called before the parent constructor, so various properties may not be |
141 |
* intialized yet. |
142 |
* |
143 |
* @param {Object[]} options Array of menu options in the format `{ data: …, label: … }` |
144 |
* @private |
145 |
*/ |
146 |
OO.ui.DropdownInputWidget.prototype.setOptionsData = function ( options ) { |
147 |
var |
148 |
optionWidgets, |
149 |
widget = this; |
150 |
|
151 |
this.optionsDirty = true; |
152 |
|
153 |
optionWidgets = options.map( function ( opt ) { |
154 |
var optValue; |
155 |
|
156 |
if ( opt.optgroup !== undefined ) { |
157 |
return widget.createMenuSectionOptionWidget( opt.optgroup ); |
158 |
} |
159 |
|
160 |
optValue = widget.cleanUpValue( opt.data ); |
161 |
return widget.createMenuOptionWidget( |
162 |
optValue, |
163 |
opt.label !== undefined ? opt.label : optValue |
164 |
); |
165 |
|
166 |
} ); |
167 |
|
168 |
this.dropdownWidget.getMenu().clearItems().addItems( optionWidgets ); |
169 |
}; |
170 |
|
171 |
/** |
172 |
* Create a menu option widget. |
173 |
* |
174 |
* @protected |
175 |
* @param {string} data Item data |
176 |
* @param {string} label Item label |
177 |
* @return {OO.ui.MenuOptionWidget} Option widget |
178 |
*/ |
179 |
OO.ui.DropdownInputWidget.prototype.createMenuOptionWidget = function ( data, label ) { |
180 |
return new OO.ui.MenuOptionWidget( { |
181 |
data: data, |
182 |
label: label |
183 |
} ); |
184 |
}; |
185 |
|
186 |
/** |
187 |
* Create a menu section option widget. |
188 |
* |
189 |
* @protected |
190 |
* @param {string} label Section item label |
191 |
* @return {OO.ui.MenuSectionOptionWidget} Menu section option widget |
192 |
*/ |
193 |
OO.ui.DropdownInputWidget.prototype.createMenuSectionOptionWidget = function ( label ) { |
194 |
return new OO.ui.MenuSectionOptionWidget( { |
195 |
label: label |
196 |
} ); |
197 |
}; |
198 |
|
199 |
/** |
200 |
* Update the user-visible interface to match the internal list of options and value. |
201 |
* |
202 |
* This method must only be called after the parent constructor. |
203 |
* |
204 |
* @private |
205 |
*/ |
206 |
OO.ui.DropdownInputWidget.prototype.updateOptionsInterface = function () { |
207 |
var |
208 |
$optionsContainer = this.$input, |
209 |
defaultValue = this.defaultValue, |
210 |
widget = this; |
211 |
|
212 |
this.$input.empty(); |
213 |
|
214 |
this.dropdownWidget.getMenu().getItems().forEach( function ( optionWidget ) { |
215 |
var $optionNode; |
216 |
|
217 |
if ( !( optionWidget instanceof OO.ui.MenuSectionOptionWidget ) ) { |
Error |
Row 218, Column 18: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 218, Column 18: "Prefer setAttribute to $.attr"
jquery/no-attr
|
218 |
$optionNode = $( '<option>' ) |
219 |
.attr( 'value', optionWidget.getData() ) |
220 |
.text( optionWidget.getLabel() ); |
221 |
|
222 |
// Remember original selection state. This property can be later used to check whether |
223 |
// the selection state of the input has been changed since it was created. |
224 |
$optionNode[ 0 ].defaultSelected = ( optionWidget.getData() === defaultValue ); |
225 |
|
226 |
$optionsContainer.append( $optionNode ); |
227 |
} else { |
Error |
Row 228, Column 18: "Prefer setAttribute to $.attr"
jquery/no-attr
|
228 |
$optionNode = $( '<optgroup>' ) |
229 |
.attr( 'label', optionWidget.getLabel() ); |
230 |
widget.$input.append( $optionNode ); |
231 |
$optionsContainer = $optionNode; |
232 |
} |
233 |
} ); |
234 |
|
235 |
this.optionsDirty = false; |
236 |
}; |
237 |
|
238 |
/** |
239 |
* @inheritdoc |
240 |
*/ |
241 |
OO.ui.DropdownInputWidget.prototype.focus = function () { |
242 |
this.dropdownWidget.focus(); |
243 |
return this; |
244 |
}; |
245 |
|
246 |
/** |
247 |
* @inheritdoc |
248 |
*/ |
249 |
OO.ui.DropdownInputWidget.prototype.blur = function () { |
250 |
this.dropdownWidget.blur(); |
251 |
return this; |
252 |
}; |
253 |
|
|
|
/src/widgets/DropdownWidget.js
|
0 problems
|
|
/src/widgets/HiddenInputWidget.js
|
0 problems
|
|
/src/widgets/IconWidget.js
|
0 problems
|
|
/src/widgets/IndicatorWidget.js
|
0 problems
|
|
/src/widgets/InputWidget.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 81, Column 18: "Prefer querySelectorAll to $.find"
jquery/no-find
|
Line |
Source |
1 |
/** |
2 |
* InputWidget is the base class for all input widgets, which |
3 |
* include {@link OO.ui.TextInputWidget text inputs}, {@link OO.ui.CheckboxInputWidget checkbox inputs}, |
4 |
* {@link OO.ui.RadioInputWidget radio inputs}, and {@link OO.ui.ButtonInputWidget button inputs}. |
5 |
* See the [OOUI documentation on MediaWiki] [1] for more information and examples. |
6 |
* |
7 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs |
8 |
* |
9 |
* @abstract |
10 |
* @class |
11 |
* @extends OO.ui.Widget |
12 |
* @mixins OO.ui.mixin.FlaggedElement |
13 |
* @mixins OO.ui.mixin.TabIndexedElement |
14 |
* @mixins OO.ui.mixin.TitledElement |
15 |
* @mixins OO.ui.mixin.AccessKeyedElement |
16 |
* |
17 |
* @constructor |
18 |
* @param {Object} [config] Configuration options |
19 |
* @cfg {string} [name=''] The value of the input’s HTML `name` attribute. |
20 |
* @cfg {string} [value=''] The value of the input. |
21 |
* @cfg {string} [dir] The directionality of the input (ltr/rtl). |
22 |
* @cfg {string} [inputId] The value of the input’s HTML `id` attribute. |
23 |
* @cfg {Function} [inputFilter] The name of an input filter function. Input filters modify the value of an input |
24 |
* before it is accepted. |
25 |
*/ |
26 |
OO.ui.InputWidget = function OoUiInputWidget( config ) { |
27 |
// Configuration initialization |
28 |
config = config || {}; |
29 |
|
30 |
// Parent constructor |
31 |
OO.ui.InputWidget.parent.call( this, config ); |
32 |
|
33 |
// Properties |
34 |
// See #reusePreInfuseDOM about config.$input |
35 |
this.$input = config.$input || this.getInputElement( config ); |
36 |
this.value = ''; |
37 |
this.inputFilter = config.inputFilter; |
38 |
|
39 |
// Mixin constructors |
40 |
OO.ui.mixin.FlaggedElement.call( this, config ); |
41 |
OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$input } ) ); |
42 |
OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$input } ) ); |
43 |
OO.ui.mixin.AccessKeyedElement.call( this, $.extend( {}, config, { $accessKeyed: this.$input } ) ); |
44 |
|
45 |
// Events |
46 |
this.$input.on( 'keydown mouseup cut paste change input select', this.onEdit.bind( this ) ); |
47 |
|
48 |
// Initialization |
49 |
this.$input |
50 |
.addClass( 'oo-ui-inputWidget-input' ) |
51 |
.attr( 'name', config.name ) |
52 |
.prop( 'disabled', this.isDisabled() ); |
53 |
this.$element |
54 |
.addClass( 'oo-ui-inputWidget' ) |
55 |
.append( this.$input ); |
56 |
this.setValue( config.value ); |
57 |
if ( config.dir ) { |
58 |
this.setDir( config.dir ); |
59 |
} |
60 |
if ( config.inputId !== undefined ) { |
61 |
this.setInputId( config.inputId ); |
62 |
} |
63 |
}; |
64 |
|
65 |
/* Setup */ |
66 |
|
67 |
OO.inheritClass( OO.ui.InputWidget, OO.ui.Widget ); |
68 |
OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.FlaggedElement ); |
69 |
OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.TabIndexedElement ); |
70 |
OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.TitledElement ); |
71 |
OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.AccessKeyedElement ); |
72 |
|
73 |
/* Static Methods */ |
74 |
|
75 |
/** |
76 |
* @inheritdoc |
77 |
*/ |
78 |
OO.ui.InputWidget.static.reusePreInfuseDOM = function ( node, config ) { |
79 |
config = OO.ui.InputWidget.parent.static.reusePreInfuseDOM( node, config ); |
80 |
// Reusing `$input` lets browsers preserve inputted values across page reloads, see T114134. |
Error |
Row 81, Column 18: "Prefer querySelectorAll to $.find"
jquery/no-find
|
81 |
config.$input = $( node ).find( '.oo-ui-inputWidget-input' ); |
82 |
return config; |
83 |
}; |
84 |
|
85 |
/** |
86 |
* @inheritdoc |
87 |
*/ |
88 |
OO.ui.InputWidget.static.gatherPreInfuseState = function ( node, config ) { |
89 |
var state = OO.ui.InputWidget.parent.static.gatherPreInfuseState( node, config ); |
90 |
if ( config.$input && config.$input.length ) { |
91 |
state.value = config.$input.val(); |
92 |
// Might be better in TabIndexedElement, but it's awkward to do there because mixins are awkward |
93 |
state.focus = config.$input.is( ':focus' ); |
94 |
} |
95 |
return state; |
96 |
}; |
97 |
|
98 |
/* Events */ |
99 |
|
100 |
/** |
101 |
* @event change |
102 |
* |
103 |
* A change event is emitted when the value of the input changes. |
104 |
* |
105 |
* @param {string} value |
106 |
*/ |
107 |
|
108 |
/* Methods */ |
109 |
|
110 |
/** |
111 |
* Get input element. |
112 |
* |
113 |
* Subclasses of OO.ui.InputWidget use the `config` parameter to produce different elements in |
114 |
* different circumstances. The element must have a `value` property (like form elements). |
115 |
* |
116 |
* @protected |
117 |
* @param {Object} config Configuration options |
118 |
* @return {jQuery} Input element |
119 |
*/ |
120 |
OO.ui.InputWidget.prototype.getInputElement = function () { |
121 |
return $( '<input>' ); |
122 |
}; |
123 |
|
124 |
/** |
125 |
* Handle potentially value-changing events. |
126 |
* |
127 |
* @private |
128 |
* @param {jQuery.Event} e Key down, mouse up, cut, paste, change, input, or select event |
129 |
*/ |
130 |
OO.ui.InputWidget.prototype.onEdit = function () { |
131 |
var widget = this; |
132 |
if ( !this.isDisabled() ) { |
133 |
// Allow the stack to clear so the value will be updated |
134 |
setTimeout( function () { |
135 |
widget.setValue( widget.$input.val() ); |
136 |
} ); |
137 |
} |
138 |
}; |
139 |
|
140 |
/** |
141 |
* Get the value of the input. |
142 |
* |
143 |
* @return {string} Input value |
144 |
*/ |
145 |
OO.ui.InputWidget.prototype.getValue = function () { |
146 |
// Resynchronize our internal data with DOM data. Other scripts executing on the page can modify |
147 |
// it, and we won't know unless they're kind enough to trigger a 'change' event. |
148 |
var value = this.$input.val(); |
149 |
if ( this.value !== value ) { |
150 |
this.setValue( value ); |
151 |
} |
152 |
return this.value; |
153 |
}; |
154 |
|
155 |
/** |
156 |
* Set the directionality of the input. |
157 |
* |
158 |
* @param {string} dir Text directionality: 'ltr', 'rtl' or 'auto' |
159 |
* @chainable |
160 |
*/ |
161 |
OO.ui.InputWidget.prototype.setDir = function ( dir ) { |
162 |
this.$input.prop( 'dir', dir ); |
163 |
return this; |
164 |
}; |
165 |
|
166 |
/** |
167 |
* Set the value of the input. |
168 |
* |
169 |
* @param {string} value New value |
170 |
* @fires change |
171 |
* @chainable |
172 |
*/ |
173 |
OO.ui.InputWidget.prototype.setValue = function ( value ) { |
174 |
value = this.cleanUpValue( value ); |
175 |
// Update the DOM if it has changed. Note that with cleanUpValue, it |
176 |
// is possible for the DOM value to change without this.value changing. |
177 |
if ( this.$input.val() !== value ) { |
178 |
this.$input.val( value ); |
179 |
} |
180 |
if ( this.value !== value ) { |
181 |
this.value = value; |
182 |
this.emit( 'change', this.value ); |
183 |
} |
184 |
// The first time that the value is set (probably while constructing the widget), |
185 |
// remember it in defaultValue. This property can be later used to check whether |
186 |
// the value of the input has been changed since it was created. |
187 |
if ( this.defaultValue === undefined ) { |
188 |
this.defaultValue = this.value; |
189 |
this.$input[ 0 ].defaultValue = this.defaultValue; |
190 |
} |
191 |
return this; |
192 |
}; |
193 |
|
194 |
/** |
195 |
* Clean up incoming value. |
196 |
* |
197 |
* Ensures value is a string, and converts undefined and null to empty string. |
198 |
* |
199 |
* @private |
200 |
* @param {string} value Original value |
201 |
* @return {string} Cleaned up value |
202 |
*/ |
203 |
OO.ui.InputWidget.prototype.cleanUpValue = function ( value ) { |
204 |
if ( value === undefined || value === null ) { |
205 |
return ''; |
206 |
} else if ( this.inputFilter ) { |
207 |
return this.inputFilter( String( value ) ); |
208 |
} else { |
209 |
return String( value ); |
210 |
} |
211 |
}; |
212 |
|
213 |
/** |
214 |
* @inheritdoc |
215 |
*/ |
216 |
OO.ui.InputWidget.prototype.setDisabled = function ( state ) { |
217 |
OO.ui.InputWidget.parent.prototype.setDisabled.call( this, state ); |
218 |
if ( this.$input ) { |
219 |
this.$input.prop( 'disabled', this.isDisabled() ); |
220 |
} |
221 |
return this; |
222 |
}; |
223 |
|
224 |
/** |
225 |
* Set the 'id' attribute of the `<input>` element. |
226 |
* |
227 |
* @param {string} id |
228 |
* @chainable |
229 |
*/ |
230 |
OO.ui.InputWidget.prototype.setInputId = function ( id ) { |
231 |
this.$input.attr( 'id', id ); |
232 |
return this; |
233 |
}; |
234 |
|
235 |
/** |
236 |
* @inheritdoc |
237 |
*/ |
238 |
OO.ui.InputWidget.prototype.restorePreInfuseState = function ( state ) { |
239 |
OO.ui.InputWidget.parent.prototype.restorePreInfuseState.call( this, state ); |
240 |
if ( state.value !== undefined && state.value !== this.getValue() ) { |
241 |
this.setValue( state.value ); |
242 |
} |
243 |
if ( state.focus ) { |
244 |
this.focus(); |
245 |
} |
246 |
}; |
247 |
|
|
|
/src/widgets/LabelWidget.js
|
0 problems
|
|
/src/widgets/MultilineTextInputWidget.js
|
0 problems
|
|
/src/widgets/MultioptionWidget.js
|
0 problems
|
|
/src/widgets/MultiselectWidget.js
|
0 problems
|
|
/src/widgets/NumberInputWidget.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 35, Column 15: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* NumberInputWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value |
3 |
* can be entered manually) and two {@link OO.ui.ButtonWidget button widgets} |
4 |
* (to adjust the value in increments) to allow the user to enter a number. |
5 |
* |
6 |
* @example |
7 |
* // Example: A NumberInputWidget. |
8 |
* var numberInput = new OO.ui.NumberInputWidget( { |
9 |
* label: 'NumberInputWidget', |
10 |
* input: { value: 5 }, |
11 |
* min: 1, |
12 |
* max: 10 |
13 |
* } ); |
14 |
* $( 'body' ).append( numberInput.$element ); |
15 |
* |
16 |
* @class |
17 |
* @extends OO.ui.TextInputWidget |
18 |
* |
19 |
* @constructor |
20 |
* @param {Object} [config] Configuration options |
21 |
* @cfg {Object} [minusButton] Configuration options to pass to the |
22 |
* {@link OO.ui.ButtonWidget decrementing button widget}. |
23 |
* @cfg {Object} [plusButton] Configuration options to pass to the |
24 |
* {@link OO.ui.ButtonWidget incrementing button widget}. |
25 |
* @cfg {number} [min=-Infinity] Minimum allowed value |
26 |
* @cfg {number} [max=Infinity] Maximum allowed value |
27 |
* @cfg {number|null} [step] If specified, the field only accepts values that are multiples of this. |
28 |
* @cfg {number} [buttonStep=step||1] Delta when using the buttons or up/down arrow keys. |
29 |
* Defaults to `step` if specified, otherwise `1`. |
30 |
* @cfg {number} [pageStep=10*buttonStep] Delta when using the page-up/page-down keys. |
31 |
* Defaults to 10 times `buttonStep`. |
32 |
* @cfg {boolean} [showButtons=true] Whether to show the plus and minus buttons. |
33 |
*/ |
34 |
OO.ui.NumberInputWidget = function OoUiNumberInputWidget( config ) { |
Error |
Row 35, Column 15: "Prefer classList to $.addClass"
jquery/no-class
|
35 |
var $field = $( '<div>' ) |
36 |
.addClass( 'oo-ui-numberInputWidget-field' ); |
37 |
|
38 |
// Configuration initialization |
39 |
config = $.extend( { |
40 |
min: -Infinity, |
41 |
max: Infinity, |
42 |
showButtons: true |
43 |
}, config ); |
44 |
|
45 |
// For backward compatibility |
46 |
$.extend( config, config.input ); |
47 |
this.input = this; |
48 |
|
49 |
// Parent constructor |
50 |
OO.ui.NumberInputWidget.parent.call( this, $.extend( config, { |
51 |
type: 'number' |
52 |
} ) ); |
53 |
|
54 |
if ( config.showButtons ) { |
55 |
this.minusButton = new OO.ui.ButtonWidget( $.extend( |
56 |
{ |
57 |
disabled: this.isDisabled(), |
58 |
tabIndex: -1, |
59 |
classes: [ 'oo-ui-numberInputWidget-minusButton' ], |
60 |
icon: 'subtract' |
61 |
}, |
62 |
config.minusButton |
63 |
) ); |
64 |
this.minusButton.$element.attr( 'aria-hidden', 'true' ); |
65 |
this.plusButton = new OO.ui.ButtonWidget( $.extend( |
66 |
{ |
67 |
disabled: this.isDisabled(), |
68 |
tabIndex: -1, |
69 |
classes: [ 'oo-ui-numberInputWidget-plusButton' ], |
70 |
icon: 'add' |
71 |
}, |
72 |
config.plusButton |
73 |
) ); |
74 |
this.plusButton.$element.attr( 'aria-hidden', 'true' ); |
75 |
} |
76 |
|
77 |
// Events |
78 |
this.$input.on( { |
79 |
keydown: this.onKeyDown.bind( this ), |
80 |
'wheel mousewheel DOMMouseScroll': this.onWheel.bind( this ) |
81 |
} ); |
82 |
if ( config.showButtons ) { |
83 |
this.plusButton.connect( this, { |
84 |
click: [ 'onButtonClick', +1 ] |
85 |
} ); |
86 |
this.minusButton.connect( this, { |
87 |
click: [ 'onButtonClick', -1 ] |
88 |
} ); |
89 |
} |
90 |
|
91 |
// Build the field |
92 |
$field.append( this.$input ); |
93 |
if ( config.showButtons ) { |
94 |
$field |
95 |
.prepend( this.minusButton.$element ) |
96 |
.append( this.plusButton.$element ); |
97 |
} |
98 |
|
99 |
// Initialization |
100 |
if ( config.allowInteger || config.isInteger ) { |
101 |
// Backward compatibility |
102 |
config.step = 1; |
103 |
} |
104 |
this.setRange( config.min, config.max ); |
105 |
this.setStep( config.buttonStep, config.pageStep, config.step ); |
106 |
// Set the validation method after we set step and range |
107 |
// so that it doesn't immediately call setValidityFlag |
108 |
this.setValidation( this.validateNumber.bind( this ) ); |
109 |
|
110 |
this.$element |
111 |
.addClass( 'oo-ui-numberInputWidget' ) |
112 |
.toggleClass( 'oo-ui-numberInputWidget-buttoned', config.showButtons ) |
113 |
.append( $field ); |
114 |
}; |
115 |
|
116 |
/* Setup */ |
117 |
|
118 |
OO.inheritClass( OO.ui.NumberInputWidget, OO.ui.TextInputWidget ); |
119 |
|
120 |
/* Methods */ |
121 |
|
122 |
// Backward compatibility |
123 |
OO.ui.NumberInputWidget.prototype.setAllowInteger = function ( flag ) { |
124 |
this.setStep( flag ? 1 : null ); |
125 |
}; |
126 |
// Backward compatibility |
127 |
OO.ui.NumberInputWidget.prototype.setIsInteger = OO.ui.NumberInputWidget.prototype.setAllowInteger; |
128 |
|
129 |
// Backward compatibility |
130 |
OO.ui.NumberInputWidget.prototype.getAllowInteger = function () { |
131 |
return this.step === 1; |
132 |
}; |
133 |
// Backward compatibility |
134 |
OO.ui.NumberInputWidget.prototype.getIsInteger = OO.ui.NumberInputWidget.prototype.getAllowInteger; |
135 |
|
136 |
/** |
137 |
* Set the range of allowed values |
138 |
* |
139 |
* @param {number} min Minimum allowed value |
140 |
* @param {number} max Maximum allowed value |
141 |
*/ |
142 |
OO.ui.NumberInputWidget.prototype.setRange = function ( min, max ) { |
143 |
if ( min > max ) { |
144 |
throw new Error( 'Minimum (' + min + ') must not be greater than maximum (' + max + ')' ); |
145 |
} |
146 |
this.min = min; |
147 |
this.max = max; |
148 |
this.$input.attr( 'min', this.min ); |
149 |
this.$input.attr( 'max', this.max ); |
150 |
this.setValidityFlag(); |
151 |
}; |
152 |
|
153 |
/** |
154 |
* Get the current range |
155 |
* |
156 |
* @return {number[]} Minimum and maximum values |
157 |
*/ |
158 |
OO.ui.NumberInputWidget.prototype.getRange = function () { |
159 |
return [ this.min, this.max ]; |
160 |
}; |
161 |
|
162 |
/** |
163 |
* Set the stepping deltas |
164 |
* |
165 |
* @param {number} [buttonStep=step||1] Delta when using the buttons or up/down arrow keys. |
166 |
* Defaults to `step` if specified, otherwise `1`. |
167 |
* @param {number} [pageStep=10*buttonStep] Delta when using the page-up/page-down keys. |
168 |
* Defaults to 10 times `buttonStep`. |
169 |
* @param {number|null} [step] If specified, the field only accepts values that are multiples of this. |
170 |
*/ |
171 |
OO.ui.NumberInputWidget.prototype.setStep = function ( buttonStep, pageStep, step ) { |
172 |
if ( buttonStep === undefined ) { |
173 |
buttonStep = step || 1; |
174 |
} |
175 |
if ( pageStep === undefined ) { |
176 |
pageStep = 10 * buttonStep; |
177 |
} |
178 |
if ( step !== null && step <= 0 ) { |
179 |
throw new Error( 'Step value, if given, must be positive' ); |
180 |
} |
181 |
if ( buttonStep <= 0 ) { |
182 |
throw new Error( 'Button step value must be positive' ); |
183 |
} |
184 |
if ( pageStep <= 0 ) { |
185 |
throw new Error( 'Page step value must be positive' ); |
186 |
} |
187 |
this.step = step; |
188 |
this.buttonStep = buttonStep; |
189 |
this.pageStep = pageStep; |
190 |
this.$input.attr( 'step', this.step || 'any' ); |
191 |
this.setValidityFlag(); |
192 |
}; |
193 |
|
194 |
/** |
195 |
* @inheritdoc |
196 |
*/ |
197 |
OO.ui.NumberInputWidget.prototype.setValue = function ( value ) { |
198 |
if ( value === '' ) { |
199 |
// Some browsers allow a value in the input even if there isn't one reported by $input.val() |
200 |
// so here we make sure an 'empty' value is actually displayed as such. |
201 |
this.$input.val( '' ); |
202 |
} |
203 |
return OO.ui.NumberInputWidget.parent.prototype.setValue.call( this, value ); |
204 |
}; |
205 |
|
206 |
/** |
207 |
* Get the current stepping values |
208 |
* |
209 |
* @return {number[]} Button step, page step, and validity step |
210 |
*/ |
211 |
OO.ui.NumberInputWidget.prototype.getStep = function () { |
212 |
return [ this.buttonStep, this.pageStep, this.step ]; |
213 |
}; |
214 |
|
215 |
/** |
216 |
* Get the current value of the widget as a number |
217 |
* |
218 |
* @return {number} May be NaN, or an invalid number |
219 |
*/ |
220 |
OO.ui.NumberInputWidget.prototype.getNumericValue = function () { |
221 |
return +this.getValue(); |
222 |
}; |
223 |
|
224 |
/** |
225 |
* Adjust the value of the widget |
226 |
* |
227 |
* @param {number} delta Adjustment amount |
228 |
*/ |
229 |
OO.ui.NumberInputWidget.prototype.adjustValue = function ( delta ) { |
230 |
var n, v = this.getNumericValue(); |
231 |
|
232 |
delta = +delta; |
233 |
if ( isNaN( delta ) || !isFinite( delta ) ) { |
234 |
throw new Error( 'Delta must be a finite number' ); |
235 |
} |
236 |
|
237 |
if ( isNaN( v ) ) { |
238 |
n = 0; |
239 |
} else { |
240 |
n = v + delta; |
241 |
n = Math.max( Math.min( n, this.max ), this.min ); |
242 |
if ( this.step ) { |
243 |
n = Math.round( n / this.step ) * this.step; |
244 |
} |
245 |
} |
246 |
|
247 |
if ( n !== v ) { |
248 |
this.setValue( n ); |
249 |
} |
250 |
}; |
251 |
/** |
252 |
* Validate input |
253 |
* |
254 |
* @private |
255 |
* @param {string} value Field value |
256 |
* @return {boolean} |
257 |
*/ |
258 |
OO.ui.NumberInputWidget.prototype.validateNumber = function ( value ) { |
259 |
var n = +value; |
260 |
if ( value === '' ) { |
261 |
return !this.isRequired(); |
262 |
} |
263 |
|
264 |
if ( isNaN( n ) || !isFinite( n ) ) { |
265 |
return false; |
266 |
} |
267 |
|
268 |
if ( this.step && Math.floor( n / this.step ) !== n / this.step ) { |
269 |
return false; |
270 |
} |
271 |
|
272 |
if ( n < this.min || n > this.max ) { |
273 |
return false; |
274 |
} |
275 |
|
276 |
return true; |
277 |
}; |
278 |
|
279 |
/** |
280 |
* Handle mouse click events. |
281 |
* |
282 |
* @private |
283 |
* @param {number} dir +1 or -1 |
284 |
*/ |
285 |
OO.ui.NumberInputWidget.prototype.onButtonClick = function ( dir ) { |
286 |
this.adjustValue( dir * this.buttonStep ); |
287 |
}; |
288 |
|
289 |
/** |
290 |
* Handle mouse wheel events. |
291 |
* |
292 |
* @private |
293 |
* @param {jQuery.Event} event |
294 |
*/ |
295 |
OO.ui.NumberInputWidget.prototype.onWheel = function ( event ) { |
296 |
var delta = 0; |
297 |
|
298 |
if ( !this.isDisabled() && this.$input.is( ':focus' ) ) { |
299 |
// Standard 'wheel' event |
300 |
if ( event.originalEvent.deltaMode !== undefined ) { |
301 |
this.sawWheelEvent = true; |
302 |
} |
303 |
if ( event.originalEvent.deltaY ) { |
304 |
delta = -event.originalEvent.deltaY; |
305 |
} else if ( event.originalEvent.deltaX ) { |
306 |
delta = event.originalEvent.deltaX; |
307 |
} |
308 |
|
309 |
// Non-standard events |
310 |
if ( !this.sawWheelEvent ) { |
311 |
if ( event.originalEvent.wheelDeltaX ) { |
312 |
delta = -event.originalEvent.wheelDeltaX; |
313 |
} else if ( event.originalEvent.wheelDeltaY ) { |
314 |
delta = event.originalEvent.wheelDeltaY; |
315 |
} else if ( event.originalEvent.wheelDelta ) { |
316 |
delta = event.originalEvent.wheelDelta; |
317 |
} else if ( event.originalEvent.detail ) { |
318 |
delta = -event.originalEvent.detail; |
319 |
} |
320 |
} |
321 |
|
322 |
if ( delta ) { |
323 |
delta = delta < 0 ? -1 : 1; |
324 |
this.adjustValue( delta * this.buttonStep ); |
325 |
} |
326 |
|
327 |
return false; |
328 |
} |
329 |
}; |
330 |
|
331 |
/** |
332 |
* Handle key down events. |
333 |
* |
334 |
* @private |
335 |
* @param {jQuery.Event} e Key down event |
336 |
*/ |
337 |
OO.ui.NumberInputWidget.prototype.onKeyDown = function ( e ) { |
338 |
if ( !this.isDisabled() ) { |
339 |
switch ( e.which ) { |
340 |
case OO.ui.Keys.UP: |
341 |
this.adjustValue( this.buttonStep ); |
342 |
return false; |
343 |
case OO.ui.Keys.DOWN: |
344 |
this.adjustValue( -this.buttonStep ); |
345 |
return false; |
346 |
case OO.ui.Keys.PAGEUP: |
347 |
this.adjustValue( this.pageStep ); |
348 |
return false; |
349 |
case OO.ui.Keys.PAGEDOWN: |
350 |
this.adjustValue( -this.pageStep ); |
351 |
return false; |
352 |
} |
353 |
} |
354 |
}; |
355 |
|
356 |
/** |
357 |
* @inheritdoc |
358 |
*/ |
359 |
OO.ui.NumberInputWidget.prototype.setDisabled = function ( disabled ) { |
360 |
// Parent method |
361 |
OO.ui.NumberInputWidget.parent.prototype.setDisabled.call( this, disabled ); |
362 |
|
363 |
if ( this.minusButton ) { |
364 |
this.minusButton.setDisabled( this.isDisabled() ); |
365 |
} |
366 |
if ( this.plusButton ) { |
367 |
this.plusButton.setDisabled( this.isDisabled() ); |
368 |
} |
369 |
|
370 |
return this; |
371 |
}; |
372 |
|
|
|
/src/widgets/OptionWidget.js
|
0 problems
|
|
/src/widgets/OutlineControlsWidget.js
|
0 problems
|
|
/src/widgets/OutlineOptionWidget.js
|
0 problems
|
|
/src/widgets/OutlineSelectWidget.js
|
0 problems
|
Severity |
Rule |
Error |
Row 129, Column 16: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 136, Column 18: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* PopupWidget is a container for content. The popup is overlaid and positioned absolutely. |
3 |
* By default, each popup has an anchor that points toward its origin. |
4 |
* Please see the [OOUI documentation on MediaWiki.org] [1] for more information and examples. |
5 |
* |
6 |
* Unlike most widgets, PopupWidget is initially hidden and must be shown by calling #toggle. |
7 |
* |
8 |
* @example |
9 |
* // A popup widget. |
10 |
* var popup = new OO.ui.PopupWidget( { |
11 |
* $content: $( '<p>Hi there!</p>' ), |
12 |
* padded: true, |
13 |
* width: 300 |
14 |
* } ); |
15 |
* |
16 |
* $( 'body' ).append( popup.$element ); |
17 |
* // To display the popup, toggle the visibility to 'true'. |
18 |
* popup.toggle( true ); |
19 |
* |
20 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Popups |
21 |
* |
22 |
* @class |
23 |
* @extends OO.ui.Widget |
24 |
* @mixins OO.ui.mixin.LabelElement |
25 |
* @mixins OO.ui.mixin.ClippableElement |
26 |
* @mixins OO.ui.mixin.FloatableElement |
27 |
* |
28 |
* @constructor |
29 |
* @param {Object} [config] Configuration options |
30 |
* @cfg {number|null} [width=320] Width of popup in pixels. Pass `null` to use automatic width. |
31 |
* @cfg {number|null} [height=null] Height of popup in pixels. Pass `null` to use automatic height. |
32 |
* @cfg {boolean} [anchor=true] Show anchor pointing to origin of popup |
33 |
* @cfg {string} [position='below'] Where to position the popup relative to $floatableContainer |
34 |
* 'above': Put popup above $floatableContainer; anchor points down to the horizontal center |
35 |
* of $floatableContainer |
36 |
* 'below': Put popup below $floatableContainer; anchor points up to the horizontal center |
37 |
* of $floatableContainer |
38 |
* 'before': Put popup to the left (LTR) / right (RTL) of $floatableContainer; anchor points |
39 |
* endwards (right/left) to the vertical center of $floatableContainer |
40 |
* 'after': Put popup to the right (LTR) / left (RTL) of $floatableContainer; anchor points |
41 |
* startwards (left/right) to the vertical center of $floatableContainer |
42 |
* @cfg {string} [align='center'] How to align the popup to $floatableContainer |
43 |
* 'forwards': If position is above/below, move the popup as far endwards (right in LTR, left in RTL) |
44 |
* as possible while still keeping the anchor within the popup; |
45 |
* if position is before/after, move the popup as far downwards as possible. |
46 |
* 'backwards': If position is above/below, move the popup as far startwards (left in LTR, right in RTL) |
47 |
* as possible while still keeping the anchor within the popup; |
48 |
* if position in before/after, move the popup as far upwards as possible. |
49 |
* 'center': Horizontally (if position is above/below) or vertically (before/after) align the center |
50 |
* of the popup with the center of $floatableContainer. |
51 |
* 'force-left': Alias for 'forwards' in LTR and 'backwards' in RTL |
52 |
* 'force-right': Alias for 'backwards' in RTL and 'forwards' in LTR |
53 |
* @cfg {boolean} [autoFlip=true] Whether to automatically switch the popup's position between |
54 |
* 'above' and 'below', or between 'before' and 'after', if there is not enough space in the |
55 |
* desired direction to display the popup without clipping |
56 |
* @cfg {jQuery} [$container] Constrain the popup to the boundaries of the specified container. |
57 |
* See the [OOUI docs on MediaWiki][3] for an example. |
58 |
* [3]: https://www.mediawiki.org/wiki/OOUI/Widgets/Popups#containerExample |
59 |
* @cfg {number} [containerPadding=10] Padding between the popup and its container, specified as a number of pixels. |
60 |
* @cfg {jQuery} [$content] Content to append to the popup's body |
61 |
* @cfg {jQuery} [$footer] Content to append to the popup's footer |
62 |
* @cfg {boolean} [autoClose=false] Automatically close the popup when it loses focus. |
63 |
* @cfg {jQuery} [$autoCloseIgnore] Elements that will not close the popup when clicked. |
64 |
* This config option is only relevant if #autoClose is set to `true`. See the [OOUI documentation on MediaWiki][2] |
65 |
* for an example. |
66 |
* [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Popups#autocloseExample |
67 |
* @cfg {boolean} [head=false] Show a popup header that contains a #label (if specified) and close |
68 |
* button. |
69 |
* @cfg {boolean} [padded=false] Add padding to the popup's body |
70 |
*/ |
71 |
OO.ui.PopupWidget = function OoUiPopupWidget( config ) { |
72 |
// Configuration initialization |
73 |
config = config || {}; |
74 |
|
75 |
// Parent constructor |
76 |
OO.ui.PopupWidget.parent.call( this, config ); |
77 |
|
78 |
// Properties (must be set before ClippableElement constructor call) |
79 |
this.$body = $( '<div>' ); |
80 |
this.$popup = $( '<div>' ); |
81 |
|
82 |
// Mixin constructors |
83 |
OO.ui.mixin.LabelElement.call( this, config ); |
84 |
OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, { |
85 |
$clippable: this.$body, |
86 |
$clippableContainer: this.$popup |
87 |
} ) ); |
88 |
OO.ui.mixin.FloatableElement.call( this, config ); |
89 |
|
90 |
// Properties |
91 |
this.$anchor = $( '<div>' ); |
92 |
// If undefined, will be computed lazily in computePosition() |
93 |
this.$container = config.$container; |
94 |
this.containerPadding = config.containerPadding !== undefined ? config.containerPadding : 10; |
95 |
this.autoClose = !!config.autoClose; |
96 |
this.transitionTimeout = null; |
97 |
this.anchored = false; |
98 |
this.onDocumentMouseDownHandler = this.onDocumentMouseDown.bind( this ); |
99 |
this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this ); |
100 |
|
101 |
// Initialization |
102 |
this.setSize( config.width, config.height ); |
103 |
this.toggleAnchor( config.anchor === undefined || config.anchor ); |
104 |
this.setAlignment( config.align || 'center' ); |
105 |
this.setPosition( config.position || 'below' ); |
106 |
this.setAutoFlip( config.autoFlip === undefined || config.autoFlip ); |
107 |
this.setAutoCloseIgnore( config.$autoCloseIgnore ); |
108 |
this.$body.addClass( 'oo-ui-popupWidget-body' ); |
109 |
this.$anchor.addClass( 'oo-ui-popupWidget-anchor' ); |
110 |
this.$popup |
111 |
.addClass( 'oo-ui-popupWidget-popup' ) |
112 |
.append( this.$body ); |
113 |
this.$element |
114 |
.addClass( 'oo-ui-popupWidget' ) |
115 |
.append( this.$popup, this.$anchor ); |
116 |
// Move content, which was added to #$element by OO.ui.Widget, to the body |
117 |
// FIXME This is gross, we should use '$body' or something for the config |
118 |
if ( config.$content instanceof jQuery ) { |
119 |
this.$body.append( config.$content ); |
120 |
} |
121 |
|
122 |
if ( config.padded ) { |
123 |
this.$body.addClass( 'oo-ui-popupWidget-body-padded' ); |
124 |
} |
125 |
|
126 |
if ( config.head ) { |
127 |
this.closeButton = new OO.ui.ButtonWidget( { framed: false, icon: 'close' } ); |
128 |
this.closeButton.connect( this, { click: 'onCloseButtonClick' } ); |
Error |
Row 129, Column 16: "Prefer classList to $.addClass"
jquery/no-class
|
129 |
this.$head = $( '<div>' ) |
130 |
.addClass( 'oo-ui-popupWidget-head' ) |
131 |
.append( this.$label, this.closeButton.$element ); |
132 |
this.$popup.prepend( this.$head ); |
133 |
} |
134 |
|
135 |
if ( config.$footer ) { |
Error |
Row 136, Column 18: "Prefer classList to $.addClass"
jquery/no-class
|
136 |
this.$footer = $( '<div>' ) |
137 |
.addClass( 'oo-ui-popupWidget-footer' ) |
138 |
.append( config.$footer ); |
139 |
this.$popup.append( this.$footer ); |
140 |
} |
141 |
|
142 |
// Initially hidden - using #toggle may cause errors if subclasses override toggle with methods |
143 |
// that reference properties not initialized at that time of parent class construction |
144 |
// TODO: Find a better way to handle post-constructor setup |
145 |
this.visible = false; |
146 |
this.$element.addClass( 'oo-ui-element-hidden' ); |
147 |
}; |
148 |
|
149 |
/* Setup */ |
150 |
|
151 |
OO.inheritClass( OO.ui.PopupWidget, OO.ui.Widget ); |
152 |
OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.LabelElement ); |
153 |
OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.ClippableElement ); |
154 |
OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.FloatableElement ); |
155 |
|
156 |
/* Events */ |
157 |
|
158 |
/** |
159 |
* @event ready |
160 |
* |
161 |
* The popup is ready: it is visible and has been positioned and clipped. |
162 |
*/ |
163 |
|
164 |
/* Methods */ |
165 |
|
166 |
/** |
167 |
* Handles document mouse down events. |
168 |
* |
169 |
* @private |
170 |
* @param {MouseEvent} e Mouse down event |
171 |
*/ |
172 |
OO.ui.PopupWidget.prototype.onDocumentMouseDown = function ( e ) { |
173 |
if ( |
174 |
this.isVisible() && |
175 |
!OO.ui.contains( this.$element.add( this.$autoCloseIgnore ).get(), e.target, true ) |
176 |
) { |
177 |
this.toggle( false ); |
178 |
} |
179 |
}; |
180 |
|
181 |
// Deprecated alias since 0.28.3 |
182 |
OO.ui.PopupWidget.prototype.onMouseDown = function () { |
183 |
OO.ui.warnDeprecation( 'onMouseDown is deprecated, use onDocumentMouseDown instead' ); |
184 |
this.onDocumentMouseDown.apply( this, arguments ); |
185 |
}; |
186 |
|
187 |
/** |
188 |
* Bind document mouse down listener. |
189 |
* |
190 |
* @private |
191 |
*/ |
192 |
OO.ui.PopupWidget.prototype.bindDocumentMouseDownListener = function () { |
193 |
// Capture clicks outside popup |
194 |
this.getElementDocument().addEventListener( 'mousedown', this.onDocumentMouseDownHandler, true ); |
195 |
// We add 'click' event because iOS safari needs to respond to this event. |
196 |
// We can't use 'touchstart' (as is usually the equivalent to 'mousedown') because |
197 |
// then it will trigger when scrolling. While iOS Safari has some reported behavior |
198 |
// of occasionally not emitting 'click' properly, that event seems to be the standard |
199 |
// that it should be emitting, so we add it to this and will operate the event handler |
200 |
// on whichever of these events was triggered first |
201 |
this.getElementDocument().addEventListener( 'click', this.onDocumentMouseDownHandler, true ); |
202 |
}; |
203 |
|
204 |
// Deprecated alias since 0.28.3 |
205 |
OO.ui.PopupWidget.prototype.bindMouseDownListener = function () { |
206 |
OO.ui.warnDeprecation( 'bindMouseDownListener is deprecated, use bindDocumentMouseDownListener instead' ); |
207 |
this.bindDocumentMouseDownListener.apply( this, arguments ); |
208 |
}; |
209 |
|
210 |
/** |
211 |
* Handles close button click events. |
212 |
* |
213 |
* @private |
214 |
*/ |
215 |
OO.ui.PopupWidget.prototype.onCloseButtonClick = function () { |
216 |
if ( this.isVisible() ) { |
217 |
this.toggle( false ); |
218 |
} |
219 |
}; |
220 |
|
221 |
/** |
222 |
* Unbind document mouse down listener. |
223 |
* |
224 |
* @private |
225 |
*/ |
226 |
OO.ui.PopupWidget.prototype.unbindDocumentMouseDownListener = function () { |
227 |
this.getElementDocument().removeEventListener( 'mousedown', this.onDocumentMouseDownHandler, true ); |
228 |
this.getElementDocument().removeEventListener( 'click', this.onDocumentMouseDownHandler, true ); |
229 |
}; |
230 |
|
231 |
// Deprecated alias since 0.28.3 |
232 |
OO.ui.PopupWidget.prototype.unbindMouseDownListener = function () { |
233 |
OO.ui.warnDeprecation( 'unbindMouseDownListener is deprecated, use unbindDocumentMouseDownListener instead' ); |
234 |
this.unbindDocumentMouseDownListener.apply( this, arguments ); |
235 |
}; |
236 |
|
237 |
/** |
238 |
* Handles document key down events. |
239 |
* |
240 |
* @private |
241 |
* @param {KeyboardEvent} e Key down event |
242 |
*/ |
243 |
OO.ui.PopupWidget.prototype.onDocumentKeyDown = function ( e ) { |
244 |
if ( |
245 |
e.which === OO.ui.Keys.ESCAPE && |
246 |
this.isVisible() |
247 |
) { |
248 |
this.toggle( false ); |
249 |
e.preventDefault(); |
250 |
e.stopPropagation(); |
251 |
} |
252 |
}; |
253 |
|
254 |
/** |
255 |
* Bind document key down listener. |
256 |
* |
257 |
* @private |
258 |
*/ |
259 |
OO.ui.PopupWidget.prototype.bindDocumentKeyDownListener = function () { |
260 |
this.getElementDocument().addEventListener( 'keydown', this.onDocumentKeyDownHandler, true ); |
261 |
}; |
262 |
|
263 |
// Deprecated alias since 0.28.3 |
264 |
OO.ui.PopupWidget.prototype.bindKeyDownListener = function () { |
265 |
OO.ui.warnDeprecation( 'bindKeyDownListener is deprecated, use bindDocumentKeyDownListener instead' ); |
266 |
this.bindDocumentKeyDownListener.apply( this, arguments ); |
267 |
}; |
268 |
|
269 |
/** |
270 |
* Unbind document key down listener. |
271 |
* |
272 |
* @private |
273 |
*/ |
274 |
OO.ui.PopupWidget.prototype.unbindDocumentKeyDownListener = function () { |
275 |
this.getElementDocument().removeEventListener( 'keydown', this.onDocumentKeyDownHandler, true ); |
276 |
}; |
277 |
|
278 |
// Deprecated alias since 0.28.3 |
279 |
OO.ui.PopupWidget.prototype.unbindKeyDownListener = function () { |
280 |
OO.ui.warnDeprecation( 'unbindKeyDownListener is deprecated, use unbindDocumentKeyDownListener instead' ); |
281 |
this.unbindDocumentKeyDownListener.apply( this, arguments ); |
282 |
}; |
283 |
|
284 |
/** |
285 |
* Show, hide, or toggle the visibility of the anchor. |
286 |
* |
287 |
* @param {boolean} [show] Show anchor, omit to toggle |
288 |
*/ |
289 |
OO.ui.PopupWidget.prototype.toggleAnchor = function ( show ) { |
290 |
show = show === undefined ? !this.anchored : !!show; |
291 |
|
292 |
if ( this.anchored !== show ) { |
293 |
if ( show ) { |
294 |
this.$element.addClass( 'oo-ui-popupWidget-anchored' ); |
295 |
this.$element.addClass( 'oo-ui-popupWidget-anchored-' + this.anchorEdge ); |
296 |
} else { |
297 |
this.$element.removeClass( 'oo-ui-popupWidget-anchored' ); |
298 |
this.$element.removeClass( 'oo-ui-popupWidget-anchored-' + this.anchorEdge ); |
299 |
} |
300 |
this.anchored = show; |
301 |
} |
302 |
}; |
303 |
|
304 |
/** |
305 |
* Change which edge the anchor appears on. |
306 |
* |
307 |
* @param {string} edge 'top', 'bottom', 'start' or 'end' |
308 |
*/ |
309 |
OO.ui.PopupWidget.prototype.setAnchorEdge = function ( edge ) { |
310 |
if ( [ 'top', 'bottom', 'start', 'end' ].indexOf( edge ) === -1 ) { |
311 |
throw new Error( 'Invalid value for edge: ' + edge ); |
312 |
} |
313 |
if ( this.anchorEdge !== null ) { |
314 |
this.$element.removeClass( 'oo-ui-popupWidget-anchored-' + this.anchorEdge ); |
315 |
} |
316 |
this.anchorEdge = edge; |
317 |
if ( this.anchored ) { |
318 |
this.$element.addClass( 'oo-ui-popupWidget-anchored-' + edge ); |
319 |
} |
320 |
}; |
321 |
|
322 |
/** |
323 |
* Check if the anchor is visible. |
324 |
* |
325 |
* @return {boolean} Anchor is visible |
326 |
*/ |
327 |
OO.ui.PopupWidget.prototype.hasAnchor = function () { |
328 |
return this.anchored; |
329 |
}; |
330 |
|
331 |
/** |
332 |
* Toggle visibility of the popup. The popup is initially hidden and must be shown by calling |
333 |
* `.toggle( true )` after its #$element is attached to the DOM. |
334 |
* |
335 |
* Do not show the popup while it is not attached to the DOM. The calculations required to display |
336 |
* it in the right place and with the right dimensions only work correctly while it is attached. |
337 |
* Side-effects may include broken interface and exceptions being thrown. This wasn't always |
338 |
* strictly enforced, so currently it only generates a warning in the browser console. |
339 |
* |
340 |
* @fires ready |
341 |
* @inheritdoc |
342 |
*/ |
343 |
OO.ui.PopupWidget.prototype.toggle = function ( show ) { |
344 |
var change, normalHeight, oppositeHeight, normalWidth, oppositeWidth; |
345 |
show = show === undefined ? !this.isVisible() : !!show; |
346 |
|
347 |
change = show !== this.isVisible(); |
348 |
|
349 |
if ( show && !this.warnedUnattached && !this.isElementAttached() ) { |
350 |
OO.ui.warnDeprecation( 'PopupWidget#toggle: Before calling this method, the popup must be attached to the DOM.' ); |
351 |
this.warnedUnattached = true; |
352 |
} |
353 |
if ( show && !this.$floatableContainer && this.isElementAttached() ) { |
354 |
// Fall back to the parent node if the floatableContainer is not set |
355 |
this.setFloatableContainer( this.$element.parent() ); |
356 |
} |
357 |
|
358 |
if ( change && show && this.autoFlip ) { |
359 |
// Reset auto-flipping before showing the popup again. It's possible we no longer need to flip |
360 |
// (e.g. if the user scrolled). |
361 |
this.isAutoFlipped = false; |
362 |
} |
363 |
|
364 |
// Parent method |
365 |
OO.ui.PopupWidget.parent.prototype.toggle.call( this, show ); |
366 |
|
367 |
if ( change ) { |
368 |
this.togglePositioning( show && !!this.$floatableContainer ); |
369 |
|
370 |
if ( show ) { |
371 |
if ( this.autoClose ) { |
372 |
this.bindDocumentMouseDownListener(); |
373 |
this.bindDocumentKeyDownListener(); |
374 |
} |
375 |
this.updateDimensions(); |
376 |
this.toggleClipping( true ); |
377 |
|
378 |
if ( this.autoFlip ) { |
379 |
if ( this.popupPosition === 'above' || this.popupPosition === 'below' ) { |
380 |
if ( this.isClippedVertically() || this.isFloatableOutOfView() ) { |
381 |
// If opening the popup in the normal direction causes it to be clipped, open |
382 |
// in the opposite one instead |
383 |
normalHeight = this.$element.height(); |
384 |
this.isAutoFlipped = !this.isAutoFlipped; |
385 |
this.position(); |
386 |
if ( this.isClippedVertically() || this.isFloatableOutOfView() ) { |
387 |
// If that also causes it to be clipped, open in whichever direction |
388 |
// we have more space |
389 |
oppositeHeight = this.$element.height(); |
390 |
if ( oppositeHeight < normalHeight ) { |
391 |
this.isAutoFlipped = !this.isAutoFlipped; |
392 |
this.position(); |
393 |
} |
394 |
} |
395 |
} |
396 |
} |
397 |
if ( this.popupPosition === 'before' || this.popupPosition === 'after' ) { |
398 |
if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) { |
399 |
// If opening the popup in the normal direction causes it to be clipped, open |
400 |
// in the opposite one instead |
401 |
normalWidth = this.$element.width(); |
402 |
this.isAutoFlipped = !this.isAutoFlipped; |
403 |
// Due to T180173 horizontally clipped PopupWidgets have messed up dimensions, |
404 |
// which causes positioning to be off. Toggle clipping back and fort to work around. |
405 |
this.toggleClipping( false ); |
406 |
this.position(); |
407 |
this.toggleClipping( true ); |
408 |
if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) { |
409 |
// If that also causes it to be clipped, open in whichever direction |
410 |
// we have more space |
411 |
oppositeWidth = this.$element.width(); |
412 |
if ( oppositeWidth < normalWidth ) { |
413 |
this.isAutoFlipped = !this.isAutoFlipped; |
414 |
// Due to T180173 horizontally clipped PopupWidgets have messed up dimensions, |
415 |
// which causes positioning to be off. Toggle clipping back and fort to work around. |
416 |
this.toggleClipping( false ); |
417 |
this.position(); |
418 |
this.toggleClipping( true ); |
419 |
} |
420 |
} |
421 |
} |
422 |
} |
423 |
} |
424 |
|
425 |
this.emit( 'ready' ); |
426 |
} else { |
427 |
this.toggleClipping( false ); |
428 |
if ( this.autoClose ) { |
429 |
this.unbindDocumentMouseDownListener(); |
430 |
this.unbindDocumentKeyDownListener(); |
431 |
} |
432 |
} |
433 |
} |
434 |
|
435 |
return this; |
436 |
}; |
437 |
|
438 |
/** |
439 |
* Set the size of the popup. |
440 |
* |
441 |
* Changing the size may also change the popup's position depending on the alignment. |
442 |
* |
443 |
* @param {number|null} [width=320] Width in pixels. Pass `null` to use automatic width. |
444 |
* @param {number|null} [height=null] Height in pixels. Pass `null` to use automatic height. |
445 |
* @param {boolean} [transition=false] Use a smooth transition |
446 |
* @chainable |
447 |
*/ |
448 |
OO.ui.PopupWidget.prototype.setSize = function ( width, height, transition ) { |
449 |
this.width = width !== undefined ? width : 320; |
450 |
this.height = height !== undefined ? height : null; |
451 |
if ( this.isVisible() ) { |
452 |
this.updateDimensions( transition ); |
453 |
} |
454 |
}; |
455 |
|
456 |
/** |
457 |
* Update the size and position. |
458 |
* |
459 |
* Only use this to keep the popup properly anchored. Use #setSize to change the size, and this will |
460 |
* be called automatically. |
461 |
* |
462 |
* @param {boolean} [transition=false] Use a smooth transition |
463 |
* @chainable |
464 |
*/ |
465 |
OO.ui.PopupWidget.prototype.updateDimensions = function ( transition ) { |
466 |
var widget = this; |
467 |
|
468 |
// Prevent transition from being interrupted |
469 |
clearTimeout( this.transitionTimeout ); |
470 |
if ( transition ) { |
471 |
// Enable transition |
472 |
this.$element.addClass( 'oo-ui-popupWidget-transitioning' ); |
473 |
} |
474 |
|
475 |
this.position(); |
476 |
|
477 |
if ( transition ) { |
478 |
// Prevent transitioning after transition is complete |
479 |
this.transitionTimeout = setTimeout( function () { |
480 |
widget.$element.removeClass( 'oo-ui-popupWidget-transitioning' ); |
481 |
}, 200 ); |
482 |
} else { |
483 |
// Prevent transitioning immediately |
484 |
this.$element.removeClass( 'oo-ui-popupWidget-transitioning' ); |
485 |
} |
486 |
}; |
487 |
|
488 |
/** |
489 |
* @inheritdoc |
490 |
*/ |
491 |
OO.ui.PopupWidget.prototype.computePosition = function () { |
492 |
var direction, align, vertical, start, end, near, far, sizeProp, popupSize, anchorSize, anchorPos, |
493 |
anchorOffset, anchorMargin, parentPosition, positionProp, positionAdjustment, floatablePos, |
494 |
offsetParentPos, containerPos, popupPosition, viewportSpacing, |
495 |
popupPos = {}, |
496 |
anchorCss = { left: '', right: '', top: '', bottom: '' }, |
497 |
popupPositionOppositeMap = { |
498 |
above: 'below', |
499 |
below: 'above', |
500 |
before: 'after', |
501 |
after: 'before' |
502 |
}, |
503 |
alignMap = { |
504 |
ltr: { |
505 |
'force-left': 'backwards', |
506 |
'force-right': 'forwards' |
507 |
}, |
508 |
rtl: { |
509 |
'force-left': 'forwards', |
510 |
'force-right': 'backwards' |
511 |
} |
512 |
}, |
513 |
anchorEdgeMap = { |
514 |
above: 'bottom', |
515 |
below: 'top', |
516 |
before: 'end', |
517 |
after: 'start' |
518 |
}, |
519 |
hPosMap = { |
520 |
forwards: 'start', |
521 |
center: 'center', |
522 |
backwards: this.anchored ? 'before' : 'end' |
523 |
}, |
524 |
vPosMap = { |
525 |
forwards: 'top', |
526 |
center: 'center', |
527 |
backwards: 'bottom' |
528 |
}; |
529 |
|
530 |
if ( !this.$container ) { |
531 |
// Lazy-initialize $container if not specified in constructor |
532 |
this.$container = $( this.getClosestScrollableElementContainer() ); |
533 |
} |
534 |
direction = this.$container.css( 'direction' ); |
535 |
|
536 |
// Set height and width before we do anything else, since it might cause our measurements |
537 |
// to change (e.g. due to scrollbars appearing or disappearing), and it also affects centering |
538 |
this.$popup.css( { |
539 |
width: this.width !== null ? this.width : 'auto', |
540 |
height: this.height !== null ? this.height : 'auto' |
541 |
} ); |
542 |
|
543 |
align = alignMap[ direction ][ this.align ] || this.align; |
544 |
popupPosition = this.popupPosition; |
545 |
if ( this.isAutoFlipped ) { |
546 |
popupPosition = popupPositionOppositeMap[ popupPosition ]; |
547 |
} |
548 |
|
549 |
// If the popup is positioned before or after, then the anchor positioning is vertical, otherwise horizontal |
550 |
vertical = popupPosition === 'before' || popupPosition === 'after'; |
551 |
start = vertical ? 'top' : ( direction === 'rtl' ? 'right' : 'left' ); |
552 |
end = vertical ? 'bottom' : ( direction === 'rtl' ? 'left' : 'right' ); |
553 |
near = vertical ? 'top' : 'left'; |
554 |
far = vertical ? 'bottom' : 'right'; |
555 |
sizeProp = vertical ? 'Height' : 'Width'; |
556 |
popupSize = vertical ? ( this.height || this.$popup.height() ) : ( this.width || this.$popup.width() ); |
557 |
|
558 |
this.setAnchorEdge( anchorEdgeMap[ popupPosition ] ); |
559 |
this.horizontalPosition = vertical ? popupPosition : hPosMap[ align ]; |
560 |
this.verticalPosition = vertical ? vPosMap[ align ] : popupPosition; |
561 |
|
562 |
// Parent method |
563 |
parentPosition = OO.ui.mixin.FloatableElement.prototype.computePosition.call( this ); |
564 |
// Find out which property FloatableElement used for positioning, and adjust that value |
565 |
positionProp = vertical ? |
566 |
( parentPosition.top !== '' ? 'top' : 'bottom' ) : |
567 |
( parentPosition.left !== '' ? 'left' : 'right' ); |
568 |
|
569 |
// Figure out where the near and far edges of the popup and $floatableContainer are |
570 |
floatablePos = this.$floatableContainer.offset(); |
571 |
floatablePos[ far ] = floatablePos[ near ] + this.$floatableContainer[ 'outer' + sizeProp ](); |
572 |
// Measure where the offsetParent is and compute our position based on that and parentPosition |
573 |
offsetParentPos = this.$element.offsetParent()[ 0 ] === document.documentElement ? |
574 |
{ top: 0, left: 0 } : |
575 |
this.$element.offsetParent().offset(); |
576 |
|
577 |
if ( positionProp === near ) { |
578 |
popupPos[ near ] = offsetParentPos[ near ] + parentPosition[ near ]; |
579 |
popupPos[ far ] = popupPos[ near ] + popupSize; |
580 |
} else { |
581 |
popupPos[ far ] = offsetParentPos[ near ] + |
582 |
this.$element.offsetParent()[ 'inner' + sizeProp ]() - parentPosition[ far ]; |
583 |
popupPos[ near ] = popupPos[ far ] - popupSize; |
584 |
} |
585 |
|
586 |
if ( this.anchored ) { |
587 |
// Position the anchor (which is positioned relative to the popup) to point to $floatableContainer |
588 |
anchorPos = ( floatablePos[ start ] + floatablePos[ end ] ) / 2; |
589 |
anchorOffset = ( start === far ? -1 : 1 ) * ( anchorPos - popupPos[ start ] ); |
590 |
|
591 |
// If the anchor is less than 2*anchorSize from either edge, move the popup to make more space |
592 |
// this.$anchor.width()/height() returns 0 because of the CSS trickery we use, so use scrollWidth/Height |
593 |
anchorSize = this.$anchor[ 0 ][ 'scroll' + sizeProp ]; |
594 |
anchorMargin = parseFloat( this.$anchor.css( 'margin-' + start ) ); |
595 |
if ( anchorOffset + anchorMargin < 2 * anchorSize ) { |
596 |
// Not enough space for the anchor on the start side; pull the popup startwards |
597 |
positionAdjustment = ( positionProp === start ? -1 : 1 ) * |
598 |
( 2 * anchorSize - ( anchorOffset + anchorMargin ) ); |
599 |
} else if ( anchorOffset + anchorMargin > popupSize - 2 * anchorSize ) { |
600 |
// Not enough space for the anchor on the end side; pull the popup endwards |
601 |
positionAdjustment = ( positionProp === end ? -1 : 1 ) * |
602 |
( anchorOffset + anchorMargin - ( popupSize - 2 * anchorSize ) ); |
603 |
} else { |
604 |
positionAdjustment = 0; |
605 |
} |
606 |
} else { |
607 |
positionAdjustment = 0; |
608 |
} |
609 |
|
610 |
// Check if the popup will go beyond the edge of this.$container |
611 |
containerPos = this.$container[ 0 ] === document.documentElement ? |
612 |
{ top: 0, left: 0 } : |
613 |
this.$container.offset(); |
614 |
containerPos[ far ] = containerPos[ near ] + this.$container[ 'inner' + sizeProp ](); |
615 |
if ( this.$container[ 0 ] === document.documentElement ) { |
616 |
viewportSpacing = OO.ui.getViewportSpacing(); |
617 |
containerPos[ near ] += viewportSpacing[ near ]; |
618 |
containerPos[ far ] -= viewportSpacing[ far ]; |
619 |
} |
620 |
// Take into account how much the popup will move because of the adjustments we're going to make |
621 |
popupPos[ near ] += ( positionProp === near ? 1 : -1 ) * positionAdjustment; |
622 |
popupPos[ far ] += ( positionProp === near ? 1 : -1 ) * positionAdjustment; |
623 |
if ( containerPos[ near ] + this.containerPadding > popupPos[ near ] ) { |
624 |
// Popup goes beyond the near (left/top) edge, move it to the right/bottom |
625 |
positionAdjustment += ( positionProp === near ? 1 : -1 ) * |
626 |
( containerPos[ near ] + this.containerPadding - popupPos[ near ] ); |
627 |
} else if ( containerPos[ far ] - this.containerPadding < popupPos[ far ] ) { |
628 |
// Popup goes beyond the far (right/bottom) edge, move it to the left/top |
629 |
positionAdjustment += ( positionProp === far ? 1 : -1 ) * |
630 |
( popupPos[ far ] - ( containerPos[ far ] - this.containerPadding ) ); |
631 |
} |
632 |
|
633 |
if ( this.anchored ) { |
634 |
// Adjust anchorOffset for positionAdjustment |
635 |
anchorOffset += ( positionProp === start ? -1 : 1 ) * positionAdjustment; |
636 |
|
637 |
// Position the anchor |
638 |
anchorCss[ start ] = anchorOffset; |
639 |
this.$anchor.css( anchorCss ); |
640 |
} |
641 |
|
642 |
// Move the popup if needed |
643 |
parentPosition[ positionProp ] += positionAdjustment; |
644 |
|
645 |
return parentPosition; |
646 |
}; |
647 |
|
648 |
/** |
649 |
* Set popup alignment |
650 |
* |
651 |
* @param {string} [align=center] Alignment of the popup, `center`, `force-left`, `force-right`, |
652 |
* `backwards` or `forwards`. |
653 |
*/ |
654 |
OO.ui.PopupWidget.prototype.setAlignment = function ( align ) { |
655 |
// Validate alignment |
656 |
if ( [ 'force-left', 'force-right', 'backwards', 'forwards', 'center' ].indexOf( align ) > -1 ) { |
657 |
this.align = align; |
658 |
} else { |
659 |
this.align = 'center'; |
660 |
} |
661 |
this.position(); |
662 |
}; |
663 |
|
664 |
/** |
665 |
* Get popup alignment |
666 |
* |
667 |
* @return {string} Alignment of the popup, `center`, `force-left`, `force-right`, |
668 |
* `backwards` or `forwards`. |
669 |
*/ |
670 |
OO.ui.PopupWidget.prototype.getAlignment = function () { |
671 |
return this.align; |
672 |
}; |
673 |
|
674 |
/** |
675 |
* Change the positioning of the popup. |
676 |
* |
677 |
* @param {string} position 'above', 'below', 'before' or 'after' |
678 |
*/ |
679 |
OO.ui.PopupWidget.prototype.setPosition = function ( position ) { |
680 |
if ( [ 'above', 'below', 'before', 'after' ].indexOf( position ) === -1 ) { |
681 |
position = 'below'; |
682 |
} |
683 |
this.popupPosition = position; |
684 |
this.position(); |
685 |
}; |
686 |
|
687 |
/** |
688 |
* Get popup positioning. |
689 |
* |
690 |
* @return {string} 'above', 'below', 'before' or 'after' |
691 |
*/ |
692 |
OO.ui.PopupWidget.prototype.getPosition = function () { |
693 |
return this.popupPosition; |
694 |
}; |
695 |
|
696 |
/** |
697 |
* Set popup auto-flipping. |
698 |
* |
699 |
* @param {boolean} autoFlip Whether to automatically switch the popup's position between |
700 |
* 'above' and 'below', or between 'before' and 'after', if there is not enough space in the |
701 |
* desired direction to display the popup without clipping |
702 |
*/ |
703 |
OO.ui.PopupWidget.prototype.setAutoFlip = function ( autoFlip ) { |
704 |
autoFlip = !!autoFlip; |
705 |
|
706 |
if ( this.autoFlip !== autoFlip ) { |
707 |
this.autoFlip = autoFlip; |
708 |
} |
709 |
}; |
710 |
|
711 |
/** |
712 |
* Set which elements will not close the popup when clicked. |
713 |
* |
714 |
* For auto-closing popups, clicks on these elements will not cause the popup to auto-close. |
715 |
* |
716 |
* @param {jQuery} $autoCloseIgnore Elements to ignore for auto-closing |
717 |
*/ |
718 |
OO.ui.PopupWidget.prototype.setAutoCloseIgnore = function ( $autoCloseIgnore ) { |
719 |
this.$autoCloseIgnore = $autoCloseIgnore; |
720 |
}; |
721 |
|
722 |
/** |
723 |
* Get an ID of the body element, this can be used as the |
724 |
* `aria-describedby` attribute for an input field. |
725 |
* |
726 |
* @return {string} The ID of the body element |
727 |
*/ |
728 |
OO.ui.PopupWidget.prototype.getBodyId = function () { |
729 |
var id = this.$body.attr( 'id' ); |
730 |
if ( id === undefined ) { |
731 |
id = OO.ui.generateElementId(); |
732 |
this.$body.attr( 'id', id ); |
733 |
} |
734 |
return id; |
735 |
}; |
736 |
|
|
|
/src/widgets/ProgressBarWidget.js
|
0 problems
|
|
/src/widgets/RadioInputWidget.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 87, Column 9: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Line |
Source |
1 |
/** |
2 |
* RadioInputWidget creates a single radio button. Because radio buttons are usually used as a set, |
3 |
* in most cases you will want to use a {@link OO.ui.RadioSelectWidget radio select} |
4 |
* with {@link OO.ui.RadioOptionWidget radio options} instead of this class. For more information, |
5 |
* please see the [OOUI documentation on MediaWiki][1]. |
6 |
* |
7 |
* This widget can be used inside an HTML form, such as a OO.ui.FormLayout. |
8 |
* |
9 |
* @example |
10 |
* // An example of selected, unselected, and disabled radio inputs |
11 |
* var radio1 = new OO.ui.RadioInputWidget( { |
12 |
* value: 'a', |
13 |
* selected: true |
14 |
* } ); |
15 |
* var radio2 = new OO.ui.RadioInputWidget( { |
16 |
* value: 'b' |
17 |
* } ); |
18 |
* var radio3 = new OO.ui.RadioInputWidget( { |
19 |
* value: 'c', |
20 |
* disabled: true |
21 |
* } ); |
22 |
* // Create a fieldset layout with fields for each radio button. |
23 |
* var fieldset = new OO.ui.FieldsetLayout( { |
24 |
* label: 'Radio inputs' |
25 |
* } ); |
26 |
* fieldset.addItems( [ |
27 |
* new OO.ui.FieldLayout( radio1, { label: 'Selected', align: 'inline' } ), |
28 |
* new OO.ui.FieldLayout( radio2, { label: 'Unselected', align: 'inline' } ), |
29 |
* new OO.ui.FieldLayout( radio3, { label: 'Disabled', align: 'inline' } ), |
30 |
* ] ); |
31 |
* $( 'body' ).append( fieldset.$element ); |
32 |
* |
33 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs |
34 |
* |
35 |
* @class |
36 |
* @extends OO.ui.InputWidget |
37 |
* |
38 |
* @constructor |
39 |
* @param {Object} [config] Configuration options |
40 |
* @cfg {boolean} [selected=false] Select the radio button initially. By default, the radio button is not selected. |
41 |
*/ |
42 |
OO.ui.RadioInputWidget = function OoUiRadioInputWidget( config ) { |
43 |
// Configuration initialization |
44 |
config = config || {}; |
45 |
|
46 |
// Parent constructor |
47 |
OO.ui.RadioInputWidget.parent.call( this, config ); |
48 |
|
49 |
// Initialization |
50 |
this.$element |
51 |
.addClass( 'oo-ui-radioInputWidget' ) |
52 |
// Required for pretty styling in WikimediaUI theme |
53 |
.append( $( '<span>' ) ); |
54 |
this.setSelected( config.selected !== undefined ? config.selected : false ); |
55 |
}; |
56 |
|
57 |
/* Setup */ |
58 |
|
59 |
OO.inheritClass( OO.ui.RadioInputWidget, OO.ui.InputWidget ); |
60 |
|
61 |
/* Static Properties */ |
62 |
|
63 |
/** |
64 |
* @static |
65 |
* @inheritdoc |
66 |
*/ |
67 |
OO.ui.RadioInputWidget.static.tagName = 'span'; |
68 |
|
69 |
/* Static Methods */ |
70 |
|
71 |
/** |
72 |
* @inheritdoc |
73 |
*/ |
74 |
OO.ui.RadioInputWidget.static.gatherPreInfuseState = function ( node, config ) { |
75 |
var state = OO.ui.RadioInputWidget.parent.static.gatherPreInfuseState( node, config ); |
76 |
state.checked = config.$input.prop( 'checked' ); |
77 |
return state; |
78 |
}; |
79 |
|
80 |
/* Methods */ |
81 |
|
82 |
/** |
83 |
* @inheritdoc |
84 |
* @protected |
85 |
*/ |
86 |
OO.ui.RadioInputWidget.prototype.getInputElement = function () { |
Error |
Row 87, Column 9: "Prefer setAttribute to $.attr"
jquery/no-attr
|
87 |
return $( '<input>' ).attr( 'type', 'radio' ); |
88 |
}; |
89 |
|
90 |
/** |
91 |
* @inheritdoc |
92 |
*/ |
93 |
OO.ui.RadioInputWidget.prototype.onEdit = function () { |
94 |
// RadioInputWidget doesn't track its state. |
95 |
}; |
96 |
|
97 |
/** |
98 |
* Set selection state of this radio button. |
99 |
* |
100 |
* @param {boolean} state `true` for selected |
101 |
* @chainable |
102 |
*/ |
103 |
OO.ui.RadioInputWidget.prototype.setSelected = function ( state ) { |
104 |
// RadioInputWidget doesn't track its state. |
105 |
this.$input.prop( 'checked', state ); |
106 |
// The first time that the selection state is set (probably while constructing the widget), |
107 |
// remember it in defaultSelected. This property can be later used to check whether |
108 |
// the selection state of the input has been changed since it was created. |
109 |
if ( this.defaultSelected === undefined ) { |
110 |
this.defaultSelected = state; |
111 |
this.$input[ 0 ].defaultChecked = this.defaultSelected; |
112 |
} |
113 |
return this; |
114 |
}; |
115 |
|
116 |
/** |
117 |
* Check if this radio button is selected. |
118 |
* |
119 |
* @return {boolean} Radio is selected |
120 |
*/ |
121 |
OO.ui.RadioInputWidget.prototype.isSelected = function () { |
122 |
return this.$input.prop( 'checked' ); |
123 |
}; |
124 |
|
125 |
/** |
126 |
* @inheritdoc |
127 |
*/ |
128 |
OO.ui.RadioInputWidget.prototype.simulateLabelClick = function () { |
129 |
if ( !this.isDisabled() ) { |
130 |
this.$input.click(); |
131 |
} |
132 |
this.focus(); |
133 |
}; |
134 |
|
135 |
/** |
136 |
* @inheritdoc |
137 |
*/ |
138 |
OO.ui.RadioInputWidget.prototype.restorePreInfuseState = function ( state ) { |
139 |
OO.ui.RadioInputWidget.parent.prototype.restorePreInfuseState.call( this, state ); |
140 |
if ( state.checked !== undefined && state.checked !== this.isSelected() ) { |
141 |
this.setSelected( state.checked ); |
142 |
} |
143 |
}; |
144 |
|
|
|
/src/widgets/RadioOptionWidget.js
|
0 problems
|
|
/src/widgets/RadioSelectInputWidget.js
|
3 problems (3 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 63, Column 16: "Prefer value to $.val"
jquery/no-val
|
Error |
Row 63, Column 16: "Prefer querySelectorAll to $.find"
jquery/no-find
|
Error |
Row 86, Column 9: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* RadioSelectInputWidget is a {@link OO.ui.RadioSelectWidget RadioSelectWidget} intended to be used |
3 |
* within an HTML form, such as a OO.ui.FormLayout. The selected value is synchronized with the value |
4 |
* of a hidden HTML `input` tag. Please see the [OOUI documentation on MediaWiki][1] for |
5 |
* more information about input widgets. |
6 |
* |
7 |
* This and OO.ui.DropdownInputWidget support the same configuration options. |
8 |
* |
9 |
* @example |
10 |
* // Example: A RadioSelectInputWidget with three options |
11 |
* var radioSelectInput = new OO.ui.RadioSelectInputWidget( { |
12 |
* options: [ |
13 |
* { data: 'a', label: 'First' }, |
14 |
* { data: 'b', label: 'Second'}, |
15 |
* { data: 'c', label: 'Third' } |
16 |
* ] |
17 |
* } ); |
18 |
* $( 'body' ).append( radioSelectInput.$element ); |
19 |
* |
20 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs |
21 |
* |
22 |
* @class |
23 |
* @extends OO.ui.InputWidget |
24 |
* |
25 |
* @constructor |
26 |
* @param {Object} [config] Configuration options |
27 |
* @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: … }` |
28 |
*/ |
29 |
OO.ui.RadioSelectInputWidget = function OoUiRadioSelectInputWidget( config ) { |
30 |
// Configuration initialization |
31 |
config = config || {}; |
32 |
|
33 |
// Properties (must be done before parent constructor which calls #setDisabled) |
34 |
this.radioSelectWidget = new OO.ui.RadioSelectWidget(); |
35 |
// Set up the options before parent constructor, which uses them to validate config.value. |
36 |
// Use this instead of setOptions() because this.$input is not set up yet |
37 |
this.setOptionsData( config.options || [] ); |
38 |
|
39 |
// Parent constructor |
40 |
OO.ui.RadioSelectInputWidget.parent.call( this, config ); |
41 |
|
42 |
// Events |
43 |
this.radioSelectWidget.connect( this, { select: 'onMenuSelect' } ); |
44 |
|
45 |
// Initialization |
46 |
this.$element |
47 |
.addClass( 'oo-ui-radioSelectInputWidget' ) |
48 |
.append( this.radioSelectWidget.$element ); |
49 |
this.setTabIndexedElement( this.radioSelectWidget.$tabIndexed ); |
50 |
}; |
51 |
|
52 |
/* Setup */ |
53 |
|
54 |
OO.inheritClass( OO.ui.RadioSelectInputWidget, OO.ui.InputWidget ); |
55 |
|
56 |
/* Static Methods */ |
57 |
|
58 |
/** |
59 |
* @inheritdoc |
60 |
*/ |
61 |
OO.ui.RadioSelectInputWidget.static.gatherPreInfuseState = function ( node, config ) { |
62 |
var state = OO.ui.RadioSelectInputWidget.parent.static.gatherPreInfuseState( node, config ); |
Error |
Row 63, Column 16: "Prefer value to $.val"
jquery/no-val
|
Error |
Row 63, Column 16: "Prefer querySelectorAll to $.find"
jquery/no-find
|
63 |
state.value = $( node ).find( '.oo-ui-radioInputWidget .oo-ui-inputWidget-input:checked' ).val(); |
64 |
return state; |
65 |
}; |
66 |
|
67 |
/** |
68 |
* @inheritdoc |
69 |
*/ |
70 |
OO.ui.RadioSelectInputWidget.static.reusePreInfuseDOM = function ( node, config ) { |
71 |
config = OO.ui.RadioSelectInputWidget.parent.static.reusePreInfuseDOM( node, config ); |
72 |
// Cannot reuse the `<input type=radio>` set |
73 |
delete config.$input; |
74 |
return config; |
75 |
}; |
76 |
|
77 |
/* Methods */ |
78 |
|
79 |
/** |
80 |
* @inheritdoc |
81 |
* @protected |
82 |
*/ |
83 |
OO.ui.RadioSelectInputWidget.prototype.getInputElement = function () { |
84 |
// Use this instead of <input type="hidden">, because hidden inputs do not have separate |
85 |
// 'value' and 'defaultValue' properties, and InputWidget wants to handle 'defaultValue'. |
Error |
Row 86, Column 9: "Prefer classList to $.addClass"
jquery/no-class
|
86 |
return $( '<input>' ).addClass( 'oo-ui-element-hidden' ); |
87 |
}; |
88 |
|
89 |
/** |
90 |
* Handles menu select events. |
91 |
* |
92 |
* @private |
93 |
* @param {OO.ui.RadioOptionWidget} item Selected menu item |
94 |
*/ |
95 |
OO.ui.RadioSelectInputWidget.prototype.onMenuSelect = function ( item ) { |
96 |
this.setValue( item.getData() ); |
97 |
}; |
98 |
|
99 |
/** |
100 |
* @inheritdoc |
101 |
*/ |
102 |
OO.ui.RadioSelectInputWidget.prototype.setValue = function ( value ) { |
103 |
var selected; |
104 |
value = this.cleanUpValue( value ); |
105 |
// Only allow setting values that are actually present in the dropdown |
106 |
selected = this.radioSelectWidget.findItemFromData( value ) || |
107 |
this.radioSelectWidget.findFirstSelectableItem(); |
108 |
this.radioSelectWidget.selectItem( selected ); |
109 |
value = selected ? selected.getData() : ''; |
110 |
OO.ui.RadioSelectInputWidget.parent.prototype.setValue.call( this, value ); |
111 |
return this; |
112 |
}; |
113 |
|
114 |
/** |
115 |
* @inheritdoc |
116 |
*/ |
117 |
OO.ui.RadioSelectInputWidget.prototype.setDisabled = function ( state ) { |
118 |
this.radioSelectWidget.setDisabled( state ); |
119 |
OO.ui.RadioSelectInputWidget.parent.prototype.setDisabled.call( this, state ); |
120 |
return this; |
121 |
}; |
122 |
|
123 |
/** |
124 |
* Set the options available for this input. |
125 |
* |
126 |
* @param {Object[]} options Array of menu options in the format `{ data: …, label: … }` |
127 |
* @chainable |
128 |
*/ |
129 |
OO.ui.RadioSelectInputWidget.prototype.setOptions = function ( options ) { |
130 |
var value = this.getValue(); |
131 |
|
132 |
this.setOptionsData( options ); |
133 |
|
134 |
// Re-set the value to update the visible interface (RadioSelectWidget). |
135 |
// In case the previous value is no longer an available option, select the first valid one. |
136 |
this.setValue( value ); |
137 |
|
138 |
return this; |
139 |
}; |
140 |
|
141 |
/** |
142 |
* Set the internal list of options, used e.g. by setValue() to see which options are allowed. |
143 |
* |
144 |
* This method may be called before the parent constructor, so various properties may not be |
145 |
* intialized yet. |
146 |
* |
147 |
* @param {Object[]} options Array of menu options in the format `{ data: …, label: … }` |
148 |
* @private |
149 |
*/ |
150 |
OO.ui.RadioSelectInputWidget.prototype.setOptionsData = function ( options ) { |
151 |
var widget = this; |
152 |
|
153 |
this.radioSelectWidget |
154 |
.clearItems() |
155 |
.addItems( options.map( function ( opt ) { |
156 |
var optValue = widget.cleanUpValue( opt.data ); |
157 |
return new OO.ui.RadioOptionWidget( { |
158 |
data: optValue, |
159 |
label: opt.label !== undefined ? opt.label : optValue |
160 |
} ); |
161 |
} ) ); |
162 |
}; |
163 |
|
164 |
/** |
165 |
* @inheritdoc |
166 |
*/ |
167 |
OO.ui.RadioSelectInputWidget.prototype.focus = function () { |
168 |
this.radioSelectWidget.focus(); |
169 |
return this; |
170 |
}; |
171 |
|
172 |
/** |
173 |
* @inheritdoc |
174 |
*/ |
175 |
OO.ui.RadioSelectInputWidget.prototype.blur = function () { |
176 |
this.radioSelectWidget.blur(); |
177 |
return this; |
178 |
}; |
179 |
|
|
|
/src/widgets/RadioSelectWidget.js
|
0 problems
|
|
/src/widgets/SearchInputWidget.js
|
0 problems
|
|
/src/widgets/SearchWidget.js
|
0 problems
|
|
/src/widgets/SelectFileWidget.js
|
8 problems (8 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 108, Column 21: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 119, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 119, Column 5: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 150, Column 12: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 238, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 238, Column 5: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 282, Column 17: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
Error |
Row 329, Column 16: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Line |
Source |
1 |
/** |
2 |
* SelectFileWidgets allow for selecting files, using the HTML5 File API. These |
3 |
* widgets can be configured with {@link OO.ui.mixin.IconElement icons} and {@link |
4 |
* OO.ui.mixin.IndicatorElement indicators}. |
5 |
* Please see the [OOUI documentation on MediaWiki] [1] for more information and examples. |
6 |
* |
7 |
* @example |
8 |
* // Example of a file select widget |
9 |
* var selectFile = new OO.ui.SelectFileWidget(); |
10 |
* $( 'body' ).append( selectFile.$element ); |
11 |
* |
12 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets |
13 |
* |
14 |
* @class |
15 |
* @extends OO.ui.Widget |
16 |
* @mixins OO.ui.mixin.IconElement |
17 |
* @mixins OO.ui.mixin.IndicatorElement |
18 |
* @mixins OO.ui.mixin.PendingElement |
19 |
* @mixins OO.ui.mixin.LabelElement |
20 |
* |
21 |
* @constructor |
22 |
* @param {Object} [config] Configuration options |
23 |
* @cfg {string[]|null} [accept=null] MIME types to accept. null accepts all types. |
24 |
* @cfg {string} [placeholder] Text to display when no file is selected. |
25 |
* @cfg {string} [notsupported] Text to display when file support is missing in the browser. |
26 |
* @cfg {boolean} [droppable=true] Whether to accept files by drag and drop. |
27 |
* @cfg {boolean} [showDropTarget=false] Whether to show a drop target. Requires droppable to be true. |
28 |
* @cfg {number} [thumbnailSizeLimit=20] File size limit in MiB above which to not try and show a |
29 |
* preview (for performance) |
30 |
*/ |
31 |
OO.ui.SelectFileWidget = function OoUiSelectFileWidget( config ) { |
32 |
var dragHandler; |
33 |
|
34 |
// Configuration initialization |
35 |
config = $.extend( { |
36 |
accept: null, |
37 |
placeholder: OO.ui.msg( 'ooui-selectfile-placeholder' ), |
38 |
notsupported: OO.ui.msg( 'ooui-selectfile-not-supported' ), |
39 |
droppable: true, |
40 |
showDropTarget: false, |
41 |
thumbnailSizeLimit: 20 |
42 |
}, config ); |
43 |
|
44 |
// Parent constructor |
45 |
OO.ui.SelectFileWidget.parent.call( this, config ); |
46 |
|
47 |
// Mixin constructors |
48 |
OO.ui.mixin.IconElement.call( this, config ); |
49 |
OO.ui.mixin.IndicatorElement.call( this, config ); |
50 |
OO.ui.mixin.PendingElement.call( this, $.extend( {}, config, { $pending: this.$info } ) ); |
51 |
OO.ui.mixin.LabelElement.call( this, config ); |
52 |
|
53 |
// Properties |
54 |
this.$info = $( '<span>' ); |
55 |
this.showDropTarget = config.showDropTarget; |
56 |
this.thumbnailSizeLimit = config.thumbnailSizeLimit; |
57 |
this.isSupported = this.constructor.static.isSupported(); |
58 |
this.currentFile = null; |
59 |
if ( Array.isArray( config.accept ) ) { |
60 |
this.accept = config.accept; |
61 |
} else { |
62 |
this.accept = null; |
63 |
} |
64 |
this.placeholder = config.placeholder; |
65 |
this.notsupported = config.notsupported; |
66 |
this.onFileSelectedHandler = this.onFileSelected.bind( this ); |
67 |
|
68 |
this.selectButton = new OO.ui.ButtonWidget( { |
69 |
$element: $( '<label>' ), |
70 |
classes: [ 'oo-ui-selectFileWidget-selectButton' ], |
71 |
label: OO.ui.msg( 'ooui-selectfile-button-select' ), |
72 |
disabled: this.disabled || !this.isSupported |
73 |
} ); |
74 |
|
75 |
this.clearButton = new OO.ui.ButtonWidget( { |
76 |
classes: [ 'oo-ui-selectFileWidget-clearButton' ], |
77 |
framed: false, |
78 |
icon: 'close', |
79 |
disabled: this.disabled |
80 |
} ); |
81 |
|
82 |
// Events |
83 |
this.selectButton.$button.on( { |
84 |
keypress: this.onKeyPress.bind( this ) |
85 |
} ); |
86 |
this.clearButton.connect( this, { |
87 |
click: 'onClearClick' |
88 |
} ); |
89 |
if ( config.droppable ) { |
90 |
dragHandler = this.onDragEnterOrOver.bind( this ); |
91 |
this.$element.on( { |
92 |
dragenter: dragHandler, |
93 |
dragover: dragHandler, |
94 |
dragleave: this.onDragLeave.bind( this ), |
95 |
drop: this.onDrop.bind( this ) |
96 |
} ); |
97 |
} |
98 |
|
99 |
// Initialization |
100 |
this.addInput(); |
101 |
this.$label.addClass( 'oo-ui-selectFileWidget-label' ); |
102 |
this.$info |
103 |
.addClass( 'oo-ui-selectFileWidget-info' ) |
104 |
.append( this.$icon, this.$label, this.clearButton.$element, this.$indicator ); |
105 |
|
106 |
if ( config.droppable && config.showDropTarget ) { |
107 |
this.selectButton.setIcon( 'upload' ); |
Error |
Row 108, Column 21: "Prefer classList to $.addClass"
jquery/no-class
|
108 |
this.$thumbnail = $( '<div>' ).addClass( 'oo-ui-selectFileWidget-thumbnail' ); |
109 |
this.setPendingElement( this.$thumbnail ); |
110 |
this.$element |
111 |
.addClass( 'oo-ui-selectFileWidget-dropTarget oo-ui-selectFileWidget' ) |
112 |
.on( { |
113 |
click: this.onDropTargetClick.bind( this ) |
114 |
} ) |
115 |
.append( |
116 |
this.$thumbnail, |
117 |
this.$info, |
118 |
this.selectButton.$element, |
Error |
Row 119, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 119, Column 5: "Prefer classList to $.addClass"
jquery/no-class
|
119 |
$( '<span>' ) |
120 |
.addClass( 'oo-ui-selectFileWidget-dropLabel' ) |
121 |
.text( OO.ui.msg( 'ooui-selectfile-dragdrop-placeholder' ) ) |
122 |
); |
123 |
} else { |
124 |
this.$element |
125 |
.addClass( 'oo-ui-selectFileWidget' ) |
126 |
.append( this.$info, this.selectButton.$element ); |
127 |
} |
128 |
this.updateUI(); |
129 |
}; |
130 |
|
131 |
/* Setup */ |
132 |
|
133 |
OO.inheritClass( OO.ui.SelectFileWidget, OO.ui.Widget ); |
134 |
OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.IconElement ); |
135 |
OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.IndicatorElement ); |
136 |
OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.PendingElement ); |
137 |
OO.mixinClass( OO.ui.SelectFileWidget, OO.ui.mixin.LabelElement ); |
138 |
|
139 |
/* Static Properties */ |
140 |
|
141 |
/** |
142 |
* Check if this widget is supported |
143 |
* |
144 |
* @static |
145 |
* @return {boolean} |
146 |
*/ |
147 |
OO.ui.SelectFileWidget.static.isSupported = function () { |
148 |
var $input; |
149 |
if ( OO.ui.SelectFileWidget.static.isSupportedCache === null ) { |
Error |
Row 150, Column 12: "Prefer setAttribute to $.attr"
jquery/no-attr
|
150 |
$input = $( '<input>' ).attr( 'type', 'file' ); |
151 |
OO.ui.SelectFileWidget.static.isSupportedCache = $input[ 0 ].files !== undefined; |
152 |
} |
153 |
return OO.ui.SelectFileWidget.static.isSupportedCache; |
154 |
}; |
155 |
|
156 |
OO.ui.SelectFileWidget.static.isSupportedCache = null; |
157 |
|
158 |
/* Events */ |
159 |
|
160 |
/** |
161 |
* @event change |
162 |
* |
163 |
* A change event is emitted when the on/off state of the toggle changes. |
164 |
* |
165 |
* @param {File|null} value New value |
166 |
*/ |
167 |
|
168 |
/* Methods */ |
169 |
|
170 |
/** |
171 |
* Get the current value of the field |
172 |
* |
173 |
* @return {File|null} |
174 |
*/ |
175 |
OO.ui.SelectFileWidget.prototype.getValue = function () { |
176 |
return this.currentFile; |
177 |
}; |
178 |
|
179 |
/** |
180 |
* Set the current value of the field |
181 |
* |
182 |
* @param {File|null} file File to select |
183 |
*/ |
184 |
OO.ui.SelectFileWidget.prototype.setValue = function ( file ) { |
185 |
if ( this.currentFile !== file ) { |
186 |
this.currentFile = file; |
187 |
this.updateUI(); |
188 |
this.emit( 'change', this.currentFile ); |
189 |
} |
190 |
}; |
191 |
|
192 |
/** |
193 |
* Focus the widget. |
194 |
* |
195 |
* Focusses the select file button. |
196 |
* |
197 |
* @chainable |
198 |
*/ |
199 |
OO.ui.SelectFileWidget.prototype.focus = function () { |
200 |
this.selectButton.focus(); |
201 |
return this; |
202 |
}; |
203 |
|
204 |
/** |
205 |
* Blur the widget. |
206 |
* |
207 |
* @chainable |
208 |
*/ |
209 |
OO.ui.SelectFileWidget.prototype.blur = function () { |
210 |
this.selectButton.blur(); |
211 |
return this; |
212 |
}; |
213 |
|
214 |
/** |
215 |
* @inheritdoc |
216 |
*/ |
217 |
OO.ui.SelectFileWidget.prototype.simulateLabelClick = function () { |
218 |
this.focus(); |
219 |
}; |
220 |
|
221 |
/** |
222 |
* Update the user interface when a file is selected or unselected |
223 |
* |
224 |
* @protected |
225 |
*/ |
226 |
OO.ui.SelectFileWidget.prototype.updateUI = function () { |
227 |
var $label; |
228 |
if ( !this.isSupported ) { |
229 |
this.$element.addClass( 'oo-ui-selectFileWidget-notsupported' ); |
230 |
this.$element.removeClass( 'oo-ui-selectFileWidget-empty' ); |
231 |
this.setLabel( this.notsupported ); |
232 |
} else { |
233 |
this.$element.addClass( 'oo-ui-selectFileWidget-supported' ); |
234 |
if ( this.currentFile ) { |
235 |
this.$element.removeClass( 'oo-ui-selectFileWidget-empty' ); |
236 |
$label = $( [] ); |
237 |
$label = $label.add( |
Error |
Row 238, Column 5: "Prefer textContent to $.text"
jquery/no-text
|
Error |
Row 238, Column 5: "Prefer classList to $.addClass"
jquery/no-class
|
238 |
$( '<span>' ) |
239 |
.addClass( 'oo-ui-selectFileWidget-fileName' ) |
240 |
.text( this.currentFile.name ) |
241 |
); |
242 |
this.setLabel( $label ); |
243 |
|
244 |
if ( this.showDropTarget ) { |
245 |
this.pushPending(); |
246 |
this.loadAndGetImageUrl().done( function ( url ) { |
247 |
this.$thumbnail.css( 'background-image', 'url( ' + url + ' )' ); |
248 |
}.bind( this ) ).fail( function () { |
249 |
this.$thumbnail.append( |
250 |
new OO.ui.IconWidget( { |
251 |
icon: 'attachment', |
252 |
classes: [ 'oo-ui-selectFileWidget-noThumbnail-icon' ] |
253 |
} ).$element |
254 |
); |
255 |
}.bind( this ) ).always( function () { |
256 |
this.popPending(); |
257 |
}.bind( this ) ); |
258 |
this.$element.off( 'click' ); |
259 |
} |
260 |
} else { |
261 |
if ( this.showDropTarget ) { |
262 |
this.$element.off( 'click' ); |
263 |
this.$element.on( { |
264 |
click: this.onDropTargetClick.bind( this ) |
265 |
} ); |
266 |
this.$thumbnail |
267 |
.empty() |
268 |
.css( 'background-image', '' ); |
269 |
} |
270 |
this.$element.addClass( 'oo-ui-selectFileWidget-empty' ); |
271 |
this.setLabel( this.placeholder ); |
272 |
} |
273 |
} |
274 |
}; |
275 |
|
276 |
/** |
277 |
* If the selected file is an image, get its URL and load it. |
278 |
* |
279 |
* @return {jQuery.Promise} Promise resolves with the image URL after it has loaded |
280 |
*/ |
281 |
OO.ui.SelectFileWidget.prototype.loadAndGetImageUrl = function () { |
Error |
Row 282, Column 17: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
282 |
var deferred = $.Deferred(), |
283 |
file = this.currentFile, |
284 |
reader = new FileReader(); |
285 |
|
286 |
if ( |
287 |
file && |
288 |
( OO.getProp( file, 'type' ) || '' ).indexOf( 'image/' ) === 0 && |
289 |
file.size < this.thumbnailSizeLimit * 1024 * 1024 |
290 |
) { |
291 |
reader.onload = function ( event ) { |
292 |
var img = document.createElement( 'img' ); |
293 |
img.addEventListener( 'load', function () { |
294 |
if ( |
295 |
img.naturalWidth === 0 || |
296 |
img.naturalHeight === 0 || |
297 |
img.complete === false |
298 |
) { |
299 |
deferred.reject(); |
300 |
} else { |
301 |
deferred.resolve( event.target.result ); |
302 |
} |
303 |
} ); |
304 |
img.src = event.target.result; |
305 |
}; |
306 |
reader.readAsDataURL( file ); |
307 |
} else { |
308 |
deferred.reject(); |
309 |
} |
310 |
|
311 |
return deferred.promise(); |
312 |
}; |
313 |
|
314 |
/** |
315 |
* Add the input to the widget |
316 |
* |
317 |
* @private |
318 |
*/ |
319 |
OO.ui.SelectFileWidget.prototype.addInput = function () { |
320 |
if ( this.$input ) { |
321 |
this.$input.remove(); |
322 |
} |
323 |
|
324 |
if ( !this.isSupported ) { |
325 |
this.$input = null; |
326 |
return; |
327 |
} |
328 |
|
Error |
Row 329, Column 16: "Prefer setAttribute to $.attr"
jquery/no-attr
|
329 |
this.$input = $( '<input>' ).attr( 'type', 'file' ); |
330 |
this.$input.on( 'change', this.onFileSelectedHandler ); |
331 |
this.$input.on( 'click', function ( e ) { |
332 |
// Prevents dropTarget to get clicked which calls |
333 |
// a click on this input |
334 |
e.stopPropagation(); |
335 |
} ); |
336 |
this.$input.attr( { |
337 |
tabindex: -1 |
338 |
} ); |
339 |
if ( this.accept ) { |
340 |
this.$input.attr( 'accept', this.accept.join( ', ' ) ); |
341 |
} |
342 |
this.selectButton.$button.append( this.$input ); |
343 |
}; |
344 |
|
345 |
/** |
346 |
* Determine if we should accept this file |
347 |
* |
348 |
* @private |
349 |
* @param {string} mimeType File MIME type |
350 |
* @return {boolean} |
351 |
*/ |
352 |
OO.ui.SelectFileWidget.prototype.isAllowedType = function ( mimeType ) { |
353 |
var i, mimeTest; |
354 |
|
355 |
if ( !this.accept || !mimeType ) { |
356 |
return true; |
357 |
} |
358 |
|
359 |
for ( i = 0; i < this.accept.length; i++ ) { |
360 |
mimeTest = this.accept[ i ]; |
361 |
if ( mimeTest === mimeType ) { |
362 |
return true; |
363 |
} else if ( mimeTest.substr( -2 ) === '/*' ) { |
364 |
mimeTest = mimeTest.substr( 0, mimeTest.length - 1 ); |
365 |
if ( mimeType.substr( 0, mimeTest.length ) === mimeTest ) { |
366 |
return true; |
367 |
} |
368 |
} |
369 |
} |
370 |
|
371 |
return false; |
372 |
}; |
373 |
|
374 |
/** |
375 |
* Handle file selection from the input |
376 |
* |
377 |
* @private |
378 |
* @param {jQuery.Event} e |
379 |
*/ |
380 |
OO.ui.SelectFileWidget.prototype.onFileSelected = function ( e ) { |
381 |
var file = OO.getProp( e.target, 'files', 0 ) || null; |
382 |
|
383 |
if ( file && !this.isAllowedType( file.type ) ) { |
384 |
file = null; |
385 |
} |
386 |
|
387 |
this.setValue( file ); |
388 |
this.addInput(); |
389 |
}; |
390 |
|
391 |
/** |
392 |
* Handle clear button click events. |
393 |
* |
394 |
* @private |
395 |
*/ |
396 |
OO.ui.SelectFileWidget.prototype.onClearClick = function () { |
397 |
this.setValue( null ); |
398 |
return false; |
399 |
}; |
400 |
|
401 |
/** |
402 |
* Handle key press events. |
403 |
* |
404 |
* @private |
405 |
* @param {jQuery.Event} e Key press event |
406 |
*/ |
407 |
OO.ui.SelectFileWidget.prototype.onKeyPress = function ( e ) { |
408 |
if ( this.isSupported && !this.isDisabled() && this.$input && |
409 |
( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) |
410 |
) { |
411 |
this.$input.click(); |
412 |
return false; |
413 |
} |
414 |
}; |
415 |
|
416 |
/** |
417 |
* Handle drop target click events. |
418 |
* |
419 |
* @private |
420 |
* @param {jQuery.Event} e Key press event |
421 |
*/ |
422 |
OO.ui.SelectFileWidget.prototype.onDropTargetClick = function () { |
423 |
if ( this.isSupported && !this.isDisabled() && this.$input ) { |
424 |
this.$input.click(); |
425 |
return false; |
426 |
} |
427 |
}; |
428 |
|
429 |
/** |
430 |
* Handle drag enter and over events |
431 |
* |
432 |
* @private |
433 |
* @param {jQuery.Event} e Drag event |
434 |
*/ |
435 |
OO.ui.SelectFileWidget.prototype.onDragEnterOrOver = function ( e ) { |
436 |
var itemOrFile, |
437 |
droppableFile = false, |
438 |
dt = e.originalEvent.dataTransfer; |
439 |
|
440 |
e.preventDefault(); |
441 |
e.stopPropagation(); |
442 |
|
443 |
if ( this.isDisabled() || !this.isSupported ) { |
444 |
this.$element.removeClass( 'oo-ui-selectFileWidget-canDrop' ); |
445 |
dt.dropEffect = 'none'; |
446 |
return false; |
447 |
} |
448 |
|
449 |
// DataTransferItem and File both have a type property, but in Chrome files |
450 |
// have no information at this point. |
451 |
itemOrFile = OO.getProp( dt, 'items', 0 ) || OO.getProp( dt, 'files', 0 ); |
452 |
if ( itemOrFile ) { |
453 |
if ( this.isAllowedType( itemOrFile.type ) ) { |
454 |
droppableFile = true; |
455 |
} |
456 |
// dt.types is Array-like, but not an Array |
457 |
} else if ( Array.prototype.indexOf.call( OO.getProp( dt, 'types' ) || [], 'Files' ) !== -1 ) { |
458 |
// File information is not available at this point for security so just assume |
459 |
// it is acceptable for now. |
460 |
// https://bugzilla.mozilla.org/show_bug.cgi?id=640534 |
461 |
droppableFile = true; |
462 |
} |
463 |
|
464 |
this.$element.toggleClass( 'oo-ui-selectFileWidget-canDrop', droppableFile ); |
465 |
if ( !droppableFile ) { |
466 |
dt.dropEffect = 'none'; |
467 |
} |
468 |
|
469 |
return false; |
470 |
}; |
471 |
|
472 |
/** |
473 |
* Handle drag leave events |
474 |
* |
475 |
* @private |
476 |
* @param {jQuery.Event} e Drag event |
477 |
*/ |
478 |
OO.ui.SelectFileWidget.prototype.onDragLeave = function () { |
479 |
this.$element.removeClass( 'oo-ui-selectFileWidget-canDrop' ); |
480 |
}; |
481 |
|
482 |
/** |
483 |
* Handle drop events |
484 |
* |
485 |
* @private |
486 |
* @param {jQuery.Event} e Drop event |
487 |
*/ |
488 |
OO.ui.SelectFileWidget.prototype.onDrop = function ( e ) { |
489 |
var file = null, |
490 |
dt = e.originalEvent.dataTransfer; |
491 |
|
492 |
e.preventDefault(); |
493 |
e.stopPropagation(); |
494 |
this.$element.removeClass( 'oo-ui-selectFileWidget-canDrop' ); |
495 |
|
496 |
if ( this.isDisabled() || !this.isSupported ) { |
497 |
return false; |
498 |
} |
499 |
|
500 |
file = OO.getProp( dt, 'files', 0 ); |
501 |
if ( file && !this.isAllowedType( file.type ) ) { |
502 |
file = null; |
503 |
} |
504 |
if ( file ) { |
505 |
this.setValue( file ); |
506 |
} |
507 |
|
508 |
return false; |
509 |
}; |
510 |
|
511 |
/** |
512 |
* @inheritdoc |
513 |
*/ |
514 |
OO.ui.SelectFileWidget.prototype.setDisabled = function ( disabled ) { |
515 |
OO.ui.SelectFileWidget.parent.prototype.setDisabled.call( this, disabled ); |
516 |
if ( this.selectButton ) { |
517 |
this.selectButton.setDisabled( disabled ); |
518 |
} |
519 |
if ( this.clearButton ) { |
520 |
this.clearButton.setDisabled( disabled ); |
521 |
} |
522 |
return this; |
523 |
}; |
524 |
|
|
|
/src/widgets/SelectWidget.js
|
4 problems (4 errors, 0 warnings)
|
Line |
Source |
1 |
/** |
2 |
* A SelectWidget is of a generic selection of options. The OOUI library contains several types of |
3 |
* select widgets, including {@link OO.ui.ButtonSelectWidget button selects}, |
4 |
* {@link OO.ui.RadioSelectWidget radio selects}, and {@link OO.ui.MenuSelectWidget |
5 |
* menu selects}. |
6 |
* |
7 |
* This class should be used together with OO.ui.OptionWidget or OO.ui.DecoratedOptionWidget. For more |
8 |
* information, please see the [OOUI documentation on MediaWiki][1]. |
9 |
* |
10 |
* @example |
11 |
* // Example of a select widget with three options |
12 |
* var select = new OO.ui.SelectWidget( { |
13 |
* items: [ |
14 |
* new OO.ui.OptionWidget( { |
15 |
* data: 'a', |
16 |
* label: 'Option One', |
17 |
* } ), |
18 |
* new OO.ui.OptionWidget( { |
19 |
* data: 'b', |
20 |
* label: 'Option Two', |
21 |
* } ), |
22 |
* new OO.ui.OptionWidget( { |
23 |
* data: 'c', |
24 |
* label: 'Option Three', |
25 |
* } ) |
26 |
* ] |
27 |
* } ); |
28 |
* $( 'body' ).append( select.$element ); |
29 |
* |
30 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options |
31 |
* |
32 |
* @abstract |
33 |
* @class |
34 |
* @extends OO.ui.Widget |
35 |
* @mixins OO.ui.mixin.GroupWidget |
36 |
* |
37 |
* @constructor |
38 |
* @param {Object} [config] Configuration options |
39 |
* @cfg {OO.ui.OptionWidget[]} [items] An array of options to add to the select. |
40 |
* Options are created with {@link OO.ui.OptionWidget OptionWidget} classes. See |
41 |
* the [OOUI documentation on MediaWiki] [2] for examples. |
42 |
* [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options |
43 |
*/ |
44 |
OO.ui.SelectWidget = function OoUiSelectWidget( config ) { |
45 |
// Configuration initialization |
46 |
config = config || {}; |
47 |
|
48 |
// Parent constructor |
49 |
OO.ui.SelectWidget.parent.call( this, config ); |
50 |
|
51 |
// Mixin constructors |
52 |
OO.ui.mixin.GroupWidget.call( this, $.extend( {}, config, { $group: this.$element } ) ); |
53 |
|
54 |
// Properties |
55 |
this.pressed = false; |
56 |
this.selecting = null; |
57 |
this.onDocumentMouseUpHandler = this.onDocumentMouseUp.bind( this ); |
58 |
this.onDocumentMouseMoveHandler = this.onDocumentMouseMove.bind( this ); |
59 |
this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this ); |
60 |
this.onDocumentKeyPressHandler = this.onDocumentKeyPress.bind( this ); |
61 |
this.keyPressBuffer = ''; |
62 |
this.keyPressBufferTimer = null; |
63 |
this.blockMouseOverEvents = 0; |
64 |
|
65 |
// Events |
66 |
this.connect( this, { |
67 |
toggle: 'onToggle' |
68 |
} ); |
69 |
this.$element.on( { |
70 |
focusin: this.onFocus.bind( this ), |
71 |
mousedown: this.onMouseDown.bind( this ), |
72 |
mouseover: this.onMouseOver.bind( this ), |
73 |
mouseleave: this.onMouseLeave.bind( this ) |
74 |
} ); |
75 |
|
76 |
// Initialization |
77 |
this.$element |
78 |
.addClass( 'oo-ui-selectWidget oo-ui-selectWidget-depressed' ) |
79 |
.attr( 'role', 'listbox' ); |
80 |
this.setFocusOwner( this.$element ); |
81 |
if ( Array.isArray( config.items ) ) { |
82 |
this.addItems( config.items ); |
83 |
} |
84 |
}; |
85 |
|
86 |
/* Setup */ |
87 |
|
88 |
OO.inheritClass( OO.ui.SelectWidget, OO.ui.Widget ); |
89 |
OO.mixinClass( OO.ui.SelectWidget, OO.ui.mixin.GroupWidget ); |
90 |
|
91 |
/* Events */ |
92 |
|
93 |
/** |
94 |
* @event highlight |
95 |
* |
96 |
* A `highlight` event is emitted when the highlight is changed with the #highlightItem method. |
97 |
* |
98 |
* @param {OO.ui.OptionWidget|null} item Highlighted item |
99 |
*/ |
100 |
|
101 |
/** |
102 |
* @event press |
103 |
* |
104 |
* A `press` event is emitted when the #pressItem method is used to programmatically modify the |
105 |
* pressed state of an option. |
106 |
* |
107 |
* @param {OO.ui.OptionWidget|null} item Pressed item |
108 |
*/ |
109 |
|
110 |
/** |
111 |
* @event select |
112 |
* |
113 |
* A `select` event is emitted when the selection is modified programmatically with the #selectItem method. |
114 |
* |
115 |
* @param {OO.ui.OptionWidget|null} item Selected item |
116 |
*/ |
117 |
|
118 |
/** |
119 |
* @event choose |
120 |
* A `choose` event is emitted when an item is chosen with the #chooseItem method. |
121 |
* @param {OO.ui.OptionWidget} item Chosen item |
122 |
*/ |
123 |
|
124 |
/** |
125 |
* @event add |
126 |
* |
127 |
* An `add` event is emitted when options are added to the select with the #addItems method. |
128 |
* |
129 |
* @param {OO.ui.OptionWidget[]} items Added items |
130 |
* @param {number} index Index of insertion point |
131 |
*/ |
132 |
|
133 |
/** |
134 |
* @event remove |
135 |
* |
136 |
* A `remove` event is emitted when options are removed from the select with the #clearItems |
137 |
* or #removeItems methods. |
138 |
* |
139 |
* @param {OO.ui.OptionWidget[]} items Removed items |
140 |
*/ |
141 |
|
142 |
/* Methods */ |
143 |
|
144 |
/** |
145 |
* Handle focus events |
146 |
* |
147 |
* @private |
148 |
* @param {jQuery.Event} event |
149 |
*/ |
150 |
OO.ui.SelectWidget.prototype.onFocus = function ( event ) { |
151 |
var item; |
152 |
if ( event.target === this.$element[ 0 ] ) { |
153 |
// This widget was focussed, e.g. by the user tabbing to it. |
154 |
// The styles for focus state depend on one of the items being selected. |
155 |
if ( !this.findSelectedItem() ) { |
156 |
item = this.findFirstSelectableItem(); |
157 |
} |
158 |
} else { |
159 |
if ( event.target.tabIndex === -1 ) { |
160 |
// One of the options got focussed (and the event bubbled up here). |
161 |
// They can't be tabbed to, but they can be activated using accesskeys. |
162 |
// OptionWidgets and focusable UI elements inside them have tabindex="-1" set. |
163 |
item = this.findTargetItem( event ); |
164 |
} else { |
165 |
// There is something actually user-focusable in one of the labels of the options, and the |
166 |
// user focussed it (e.g. by tabbing to it). Do nothing (especially, don't change the focus). |
167 |
return; |
168 |
} |
169 |
} |
170 |
|
171 |
if ( item ) { |
172 |
if ( item.constructor.static.highlightable ) { |
173 |
this.highlightItem( item ); |
174 |
} else { |
175 |
this.selectItem( item ); |
176 |
} |
177 |
} |
178 |
|
179 |
if ( event.target !== this.$element[ 0 ] ) { |
180 |
this.$focusOwner.focus(); |
181 |
} |
182 |
}; |
183 |
|
184 |
/** |
185 |
* Handle mouse down events. |
186 |
* |
187 |
* @private |
188 |
* @param {jQuery.Event} e Mouse down event |
189 |
*/ |
190 |
OO.ui.SelectWidget.prototype.onMouseDown = function ( e ) { |
191 |
var item; |
192 |
|
193 |
if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) { |
194 |
this.togglePressed( true ); |
195 |
item = this.findTargetItem( e ); |
196 |
if ( item && item.isSelectable() ) { |
197 |
this.pressItem( item ); |
198 |
this.selecting = item; |
199 |
this.getElementDocument().addEventListener( 'mouseup', this.onDocumentMouseUpHandler, true ); |
200 |
this.getElementDocument().addEventListener( 'mousemove', this.onDocumentMouseMoveHandler, true ); |
201 |
} |
202 |
} |
203 |
return false; |
204 |
}; |
205 |
|
206 |
/** |
207 |
* Handle document mouse up events. |
208 |
* |
209 |
* @private |
210 |
* @param {MouseEvent} e Mouse up event |
211 |
*/ |
212 |
OO.ui.SelectWidget.prototype.onDocumentMouseUp = function ( e ) { |
213 |
var item; |
214 |
|
215 |
this.togglePressed( false ); |
216 |
if ( !this.selecting ) { |
217 |
item = this.findTargetItem( e ); |
218 |
if ( item && item.isSelectable() ) { |
219 |
this.selecting = item; |
220 |
} |
221 |
} |
222 |
if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT && this.selecting ) { |
223 |
this.pressItem( null ); |
224 |
this.chooseItem( this.selecting ); |
225 |
this.selecting = null; |
226 |
} |
227 |
|
228 |
this.getElementDocument().removeEventListener( 'mouseup', this.onDocumentMouseUpHandler, true ); |
229 |
this.getElementDocument().removeEventListener( 'mousemove', this.onDocumentMouseMoveHandler, true ); |
230 |
|
231 |
return false; |
232 |
}; |
233 |
|
234 |
// Deprecated alias since 0.28.3 |
235 |
OO.ui.SelectWidget.prototype.onMouseUp = function () { |
236 |
OO.ui.warnDeprecation( 'onMouseUp is deprecated, use onDocumentMouseUp instead' ); |
237 |
this.onDocumentMouseUp.apply( this, arguments ); |
238 |
}; |
239 |
|
240 |
/** |
241 |
* Handle document mouse move events. |
242 |
* |
243 |
* @private |
244 |
* @param {MouseEvent} e Mouse move event |
245 |
*/ |
246 |
OO.ui.SelectWidget.prototype.onDocumentMouseMove = function ( e ) { |
247 |
var item; |
248 |
|
249 |
if ( !this.isDisabled() && this.pressed ) { |
250 |
item = this.findTargetItem( e ); |
251 |
if ( item && item !== this.selecting && item.isSelectable() ) { |
252 |
this.pressItem( item ); |
253 |
this.selecting = item; |
254 |
} |
255 |
} |
256 |
}; |
257 |
|
258 |
// Deprecated alias since 0.28.3 |
259 |
OO.ui.SelectWidget.prototype.onMouseMove = function () { |
260 |
OO.ui.warnDeprecation( 'onMouseMove is deprecated, use onDocumentMouseMove instead' ); |
261 |
this.onDocumentMouseMove.apply( this, arguments ); |
262 |
}; |
263 |
|
264 |
/** |
265 |
* Handle mouse over events. |
266 |
* |
267 |
* @private |
268 |
* @param {jQuery.Event} e Mouse over event |
269 |
*/ |
270 |
OO.ui.SelectWidget.prototype.onMouseOver = function ( e ) { |
271 |
var item; |
272 |
if ( this.blockMouseOverEvents ) { |
273 |
return; |
274 |
} |
275 |
if ( !this.isDisabled() ) { |
276 |
item = this.findTargetItem( e ); |
277 |
this.highlightItem( item && item.isHighlightable() ? item : null ); |
278 |
} |
279 |
return false; |
280 |
}; |
281 |
|
282 |
/** |
283 |
* Handle mouse leave events. |
284 |
* |
285 |
* @private |
286 |
* @param {jQuery.Event} e Mouse over event |
287 |
*/ |
288 |
OO.ui.SelectWidget.prototype.onMouseLeave = function () { |
289 |
if ( !this.isDisabled() ) { |
290 |
this.highlightItem( null ); |
291 |
} |
292 |
return false; |
293 |
}; |
294 |
|
295 |
/** |
296 |
* Handle document key down events. |
297 |
* |
298 |
* @protected |
299 |
* @param {KeyboardEvent} e Key down event |
300 |
*/ |
301 |
OO.ui.SelectWidget.prototype.onDocumentKeyDown = function ( e ) { |
302 |
var nextItem, |
303 |
handled = false, |
304 |
currentItem = this.findHighlightedItem() || this.findSelectedItem(); |
305 |
|
306 |
if ( !this.isDisabled() && this.isVisible() ) { |
307 |
switch ( e.keyCode ) { |
308 |
case OO.ui.Keys.ENTER: |
309 |
if ( currentItem && currentItem.constructor.static.highlightable ) { |
310 |
// Was only highlighted, now let's select it. No-op if already selected. |
311 |
this.chooseItem( currentItem ); |
312 |
handled = true; |
313 |
} |
314 |
break; |
315 |
case OO.ui.Keys.UP: |
316 |
case OO.ui.Keys.LEFT: |
317 |
this.clearKeyPressBuffer(); |
318 |
nextItem = this.findRelativeSelectableItem( currentItem, -1 ); |
319 |
handled = true; |
320 |
break; |
321 |
case OO.ui.Keys.DOWN: |
322 |
case OO.ui.Keys.RIGHT: |
323 |
this.clearKeyPressBuffer(); |
324 |
nextItem = this.findRelativeSelectableItem( currentItem, 1 ); |
325 |
handled = true; |
326 |
break; |
327 |
case OO.ui.Keys.ESCAPE: |
328 |
case OO.ui.Keys.TAB: |
329 |
if ( currentItem && currentItem.constructor.static.highlightable ) { |
330 |
currentItem.setHighlighted( false ); |
331 |
} |
332 |
this.unbindDocumentKeyDownListener(); |
333 |
this.unbindDocumentKeyPressListener(); |
334 |
// Don't prevent tabbing away / defocusing |
335 |
handled = false; |
336 |
break; |
337 |
} |
338 |
|
339 |
if ( nextItem ) { |
340 |
if ( nextItem.constructor.static.highlightable ) { |
341 |
this.highlightItem( nextItem ); |
342 |
} else { |
343 |
this.chooseItem( nextItem ); |
344 |
} |
345 |
this.scrollItemIntoView( nextItem ); |
346 |
} |
347 |
|
348 |
if ( handled ) { |
349 |
e.preventDefault(); |
350 |
e.stopPropagation(); |
351 |
} |
352 |
} |
353 |
}; |
354 |
|
355 |
// Deprecated alias since 0.28.3 |
356 |
OO.ui.SelectWidget.prototype.onKeyDown = function () { |
357 |
OO.ui.warnDeprecation( 'onKeyDown is deprecated, use onDocumentKeyDown instead' ); |
358 |
this.onDocumentKeyDown.apply( this, arguments ); |
359 |
}; |
360 |
|
361 |
/** |
362 |
* Bind document key down listener. |
363 |
* |
364 |
* @protected |
365 |
*/ |
366 |
OO.ui.SelectWidget.prototype.bindDocumentKeyDownListener = function () { |
367 |
this.getElementDocument().addEventListener( 'keydown', this.onDocumentKeyDownHandler, true ); |
368 |
}; |
369 |
|
370 |
// Deprecated alias since 0.28.3 |
371 |
OO.ui.SelectWidget.prototype.bindKeyDownListener = function () { |
372 |
OO.ui.warnDeprecation( 'bindKeyDownListener is deprecated, use bindDocumentKeyDownListener instead' ); |
373 |
this.bindDocumentKeyDownListener.apply( this, arguments ); |
374 |
}; |
375 |
|
376 |
/** |
377 |
* Unbind document key down listener. |
378 |
* |
379 |
* @protected |
380 |
*/ |
381 |
OO.ui.SelectWidget.prototype.unbindDocumentKeyDownListener = function () { |
382 |
this.getElementDocument().removeEventListener( 'keydown', this.onDocumentKeyDownHandler, true ); |
383 |
}; |
384 |
|
385 |
// Deprecated alias since 0.28.3 |
386 |
OO.ui.SelectWidget.prototype.unbindKeyDownListener = function () { |
387 |
OO.ui.warnDeprecation( 'unbindKeyDownListener is deprecated, use unbindDocumentKeyDownListener instead' ); |
388 |
this.unbindDocumentKeyDownListener.apply( this, arguments ); |
389 |
}; |
390 |
|
391 |
/** |
392 |
* Scroll item into view, preventing spurious mouse highlight actions from happening. |
393 |
* |
394 |
* @param {OO.ui.OptionWidget} item Item to scroll into view |
395 |
*/ |
396 |
OO.ui.SelectWidget.prototype.scrollItemIntoView = function ( item ) { |
397 |
var widget = this; |
398 |
// Chromium's Blink engine will generate spurious 'mouseover' events during programmatic scrolling |
399 |
// and around 100-150 ms after it is finished. |
400 |
this.blockMouseOverEvents++; |
401 |
item.scrollElementIntoView().done( function () { |
402 |
setTimeout( function () { |
403 |
widget.blockMouseOverEvents--; |
404 |
}, 200 ); |
405 |
} ); |
406 |
}; |
407 |
|
408 |
/** |
409 |
* Clear the key-press buffer |
410 |
* |
411 |
* @protected |
412 |
*/ |
413 |
OO.ui.SelectWidget.prototype.clearKeyPressBuffer = function () { |
414 |
if ( this.keyPressBufferTimer ) { |
415 |
clearTimeout( this.keyPressBufferTimer ); |
416 |
this.keyPressBufferTimer = null; |
417 |
} |
418 |
this.keyPressBuffer = ''; |
419 |
}; |
420 |
|
421 |
/** |
422 |
* Handle key press events. |
423 |
* |
424 |
* @protected |
425 |
* @param {KeyboardEvent} e Key press event |
426 |
*/ |
427 |
OO.ui.SelectWidget.prototype.onDocumentKeyPress = function ( e ) { |
428 |
var c, filter, item; |
429 |
|
430 |
if ( !e.charCode ) { |
431 |
if ( e.keyCode === OO.ui.Keys.BACKSPACE && this.keyPressBuffer !== '' ) { |
432 |
this.keyPressBuffer = this.keyPressBuffer.substr( 0, this.keyPressBuffer.length - 1 ); |
433 |
return false; |
434 |
} |
435 |
return; |
436 |
} |
437 |
if ( String.fromCodePoint ) { |
438 |
c = String.fromCodePoint( e.charCode ); |
439 |
} else { |
440 |
c = String.fromCharCode( e.charCode ); |
441 |
} |
442 |
|
443 |
if ( this.keyPressBufferTimer ) { |
444 |
clearTimeout( this.keyPressBufferTimer ); |
445 |
} |
446 |
this.keyPressBufferTimer = setTimeout( this.clearKeyPressBuffer.bind( this ), 1500 ); |
447 |
|
448 |
item = this.findHighlightedItem() || this.findSelectedItem(); |
449 |
|
450 |
if ( this.keyPressBuffer === c ) { |
451 |
// Common (if weird) special case: typing "xxxx" will cycle through all |
452 |
// the items beginning with "x". |
453 |
if ( item ) { |
454 |
item = this.findRelativeSelectableItem( item, 1 ); |
455 |
} |
456 |
} else { |
457 |
this.keyPressBuffer += c; |
458 |
} |
459 |
|
460 |
filter = this.getItemMatcher( this.keyPressBuffer, false ); |
461 |
if ( !item || !filter( item ) ) { |
462 |
item = this.findRelativeSelectableItem( item, 1, filter ); |
463 |
} |
464 |
if ( item ) { |
465 |
if ( this.isVisible() && item.constructor.static.highlightable ) { |
466 |
this.highlightItem( item ); |
467 |
} else { |
468 |
this.chooseItem( item ); |
469 |
} |
470 |
this.scrollItemIntoView( item ); |
471 |
} |
472 |
|
473 |
e.preventDefault(); |
474 |
e.stopPropagation(); |
475 |
}; |
476 |
|
477 |
// Deprecated alias since 0.28.3 |
478 |
OO.ui.SelectWidget.prototype.onKeyPress = function () { |
479 |
OO.ui.warnDeprecation( 'onKeyPress is deprecated, use onDocumentKeyPress instead' ); |
480 |
this.onDocumentKeyPress.apply( this, arguments ); |
481 |
}; |
482 |
|
483 |
/** |
484 |
* Get a matcher for the specific string |
485 |
* |
486 |
* @protected |
487 |
* @param {string} s String to match against items |
488 |
* @param {boolean} [exact=false] Only accept exact matches |
489 |
* @return {Function} function ( OO.ui.OptionWidget ) => boolean |
490 |
*/ |
491 |
OO.ui.SelectWidget.prototype.getItemMatcher = function ( s, exact ) { |
492 |
var re; |
493 |
|
494 |
if ( s.normalize ) { |
495 |
s = s.normalize(); |
496 |
} |
497 |
s = exact ? s.trim() : s.replace( /^\s+/, '' ); |
498 |
re = '^\\s*' + s.replace( /([\\{}()|.?*+\-^$[\]])/g, '\\$1' ).replace( /\s+/g, '\\s+' ); |
499 |
if ( exact ) { |
500 |
re += '\\s*$'; |
501 |
} |
502 |
re = new RegExp( re, 'i' ); |
503 |
return function ( item ) { |
504 |
var matchText = item.getMatchText(); |
505 |
if ( matchText.normalize ) { |
506 |
matchText = matchText.normalize(); |
507 |
} |
508 |
return re.test( matchText ); |
509 |
}; |
510 |
}; |
511 |
|
512 |
/** |
513 |
* Bind document key press listener. |
514 |
* |
515 |
* @protected |
516 |
*/ |
517 |
OO.ui.SelectWidget.prototype.bindDocumentKeyPressListener = function () { |
518 |
this.getElementDocument().addEventListener( 'keypress', this.onDocumentKeyPressHandler, true ); |
519 |
}; |
520 |
|
521 |
// Deprecated alias since 0.28.3 |
522 |
OO.ui.SelectWidget.prototype.bindKeyPressListener = function () { |
523 |
OO.ui.warnDeprecation( 'bindKeyPressListener is deprecated, use bindDocumentKeyPressListener instead' ); |
524 |
this.bindDocumentKeyPressListener.apply( this, arguments ); |
525 |
}; |
526 |
|
527 |
/** |
528 |
* Unbind document key down listener. |
529 |
* |
530 |
* If you override this, be sure to call this.clearKeyPressBuffer() from your |
531 |
* implementation. |
532 |
* |
533 |
* @protected |
534 |
*/ |
535 |
OO.ui.SelectWidget.prototype.unbindDocumentKeyPressListener = function () { |
536 |
this.getElementDocument().removeEventListener( 'keypress', this.onDocumentKeyPressHandler, true ); |
537 |
this.clearKeyPressBuffer(); |
538 |
}; |
539 |
|
540 |
// Deprecated alias since 0.28.3 |
541 |
OO.ui.SelectWidget.prototype.unbindKeyPressListener = function () { |
542 |
OO.ui.warnDeprecation( 'unbindKeyPressListener is deprecated, use unbindDocumentKeyPressListener instead' ); |
543 |
this.unbindDocumentKeyPressListener.apply( this, arguments ); |
544 |
}; |
545 |
|
546 |
/** |
547 |
* Visibility change handler |
548 |
* |
549 |
* @protected |
550 |
* @param {boolean} visible |
551 |
*/ |
552 |
OO.ui.SelectWidget.prototype.onToggle = function ( visible ) { |
553 |
if ( !visible ) { |
554 |
this.clearKeyPressBuffer(); |
555 |
} |
556 |
}; |
557 |
|
558 |
/** |
559 |
* Get the closest item to a jQuery.Event. |
560 |
* |
561 |
* @private |
562 |
* @param {jQuery.Event} e |
563 |
* @return {OO.ui.OptionWidget|null} Outline item widget, `null` if none was found |
564 |
*/ |
565 |
OO.ui.SelectWidget.prototype.findTargetItem = function ( e ) { |
Error |
Row 566, Column 16: "Prefer closest to $.closest"
jquery/no-closest
|
566 |
var $option = $( e.target ).closest( '.oo-ui-optionWidget' ); |
Error |
Row 567, Column 8: "Prefer matches to $.is"
jquery/no-is
|
Error |
Row 567, Column 8: "Prefer closest to $.closest"
jquery/no-closest
|
567 |
if ( !$option.closest( '.oo-ui-selectWidget' ).is( this.$element ) ) { |
568 |
return null; |
569 |
} |
Error |
Row 570, Column 9: "Prefer WeakMap to $.data"
jquery/no-data
|
570 |
return $option.data( 'oo-ui-optionWidget' ) || null; |
571 |
}; |
572 |
|
573 |
/** |
574 |
* Find selected item. |
575 |
* |
576 |
* @return {OO.ui.OptionWidget|null} Selected item, `null` if no item is selected |
577 |
*/ |
578 |
OO.ui.SelectWidget.prototype.findSelectedItem = function () { |
579 |
var i, len; |
580 |
|
581 |
for ( i = 0, len = this.items.length; i < len; i++ ) { |
582 |
if ( this.items[ i ].isSelected() ) { |
583 |
return this.items[ i ]; |
584 |
} |
585 |
} |
586 |
return null; |
587 |
}; |
588 |
|
589 |
/** |
590 |
* Find highlighted item. |
591 |
* |
592 |
* @return {OO.ui.OptionWidget|null} Highlighted item, `null` if no item is highlighted |
593 |
*/ |
594 |
OO.ui.SelectWidget.prototype.findHighlightedItem = function () { |
595 |
var i, len; |
596 |
|
597 |
for ( i = 0, len = this.items.length; i < len; i++ ) { |
598 |
if ( this.items[ i ].isHighlighted() ) { |
599 |
return this.items[ i ]; |
600 |
} |
601 |
} |
602 |
return null; |
603 |
}; |
604 |
|
605 |
/** |
606 |
* Toggle pressed state. |
607 |
* |
608 |
* Press is a state that occurs when a user mouses down on an item, but |
609 |
* has not yet let go of the mouse. The item may appear selected, but it will not be selected |
610 |
* until the user releases the mouse. |
611 |
* |
612 |
* @param {boolean} pressed An option is being pressed |
613 |
*/ |
614 |
OO.ui.SelectWidget.prototype.togglePressed = function ( pressed ) { |
615 |
if ( pressed === undefined ) { |
616 |
pressed = !this.pressed; |
617 |
} |
618 |
if ( pressed !== this.pressed ) { |
619 |
this.$element |
620 |
.toggleClass( 'oo-ui-selectWidget-pressed', pressed ) |
621 |
.toggleClass( 'oo-ui-selectWidget-depressed', !pressed ); |
622 |
this.pressed = pressed; |
623 |
} |
624 |
}; |
625 |
|
626 |
/** |
627 |
* Highlight an option. If the `item` param is omitted, no options will be highlighted |
628 |
* and any existing highlight will be removed. The highlight is mutually exclusive. |
629 |
* |
630 |
* @param {OO.ui.OptionWidget} [item] Item to highlight, omit for no highlight |
631 |
* @fires highlight |
632 |
* @chainable |
633 |
*/ |
634 |
OO.ui.SelectWidget.prototype.highlightItem = function ( item ) { |
635 |
var i, len, highlighted, |
636 |
changed = false; |
637 |
|
638 |
for ( i = 0, len = this.items.length; i < len; i++ ) { |
639 |
highlighted = this.items[ i ] === item; |
640 |
if ( this.items[ i ].isHighlighted() !== highlighted ) { |
641 |
this.items[ i ].setHighlighted( highlighted ); |
642 |
changed = true; |
643 |
} |
644 |
} |
645 |
if ( changed ) { |
646 |
if ( item ) { |
647 |
this.$focusOwner.attr( 'aria-activedescendant', item.getElementId() ); |
648 |
} else { |
649 |
this.$focusOwner.removeAttr( 'aria-activedescendant' ); |
650 |
} |
651 |
this.emit( 'highlight', item ); |
652 |
} |
653 |
|
654 |
return this; |
655 |
}; |
656 |
|
657 |
/** |
658 |
* Fetch an item by its label. |
659 |
* |
660 |
* @param {string} label Label of the item to select. |
661 |
* @param {boolean} [prefix=false] Allow a prefix match, if only a single item matches |
662 |
* @return {OO.ui.Element|null} Item with equivalent label, `null` if none exists |
663 |
*/ |
664 |
OO.ui.SelectWidget.prototype.getItemFromLabel = function ( label, prefix ) { |
665 |
var i, item, found, |
666 |
len = this.items.length, |
667 |
filter = this.getItemMatcher( label, true ); |
668 |
|
669 |
for ( i = 0; i < len; i++ ) { |
670 |
item = this.items[ i ]; |
671 |
if ( item instanceof OO.ui.OptionWidget && item.isSelectable() && filter( item ) ) { |
672 |
return item; |
673 |
} |
674 |
} |
675 |
|
676 |
if ( prefix ) { |
677 |
found = null; |
678 |
filter = this.getItemMatcher( label, false ); |
679 |
for ( i = 0; i < len; i++ ) { |
680 |
item = this.items[ i ]; |
681 |
if ( item instanceof OO.ui.OptionWidget && item.isSelectable() && filter( item ) ) { |
682 |
if ( found ) { |
683 |
return null; |
684 |
} |
685 |
found = item; |
686 |
} |
687 |
} |
688 |
if ( found ) { |
689 |
return found; |
690 |
} |
691 |
} |
692 |
|
693 |
return null; |
694 |
}; |
695 |
|
696 |
/** |
697 |
* Programmatically select an option by its label. If the item does not exist, |
698 |
* all options will be deselected. |
699 |
* |
700 |
* @param {string} [label] Label of the item to select. |
701 |
* @param {boolean} [prefix=false] Allow a prefix match, if only a single item matches |
702 |
* @fires select |
703 |
* @chainable |
704 |
*/ |
705 |
OO.ui.SelectWidget.prototype.selectItemByLabel = function ( label, prefix ) { |
706 |
var itemFromLabel = this.getItemFromLabel( label, !!prefix ); |
707 |
if ( label === undefined || !itemFromLabel ) { |
708 |
return this.selectItem(); |
709 |
} |
710 |
return this.selectItem( itemFromLabel ); |
711 |
}; |
712 |
|
713 |
/** |
714 |
* Programmatically select an option by its data. If the `data` parameter is omitted, |
715 |
* or if the item does not exist, all options will be deselected. |
716 |
* |
717 |
* @param {Object|string} [data] Value of the item to select, omit to deselect all |
718 |
* @fires select |
719 |
* @chainable |
720 |
*/ |
721 |
OO.ui.SelectWidget.prototype.selectItemByData = function ( data ) { |
722 |
var itemFromData = this.findItemFromData( data ); |
723 |
if ( data === undefined || !itemFromData ) { |
724 |
return this.selectItem(); |
725 |
} |
726 |
return this.selectItem( itemFromData ); |
727 |
}; |
728 |
|
729 |
/** |
730 |
* Programmatically select an option by its reference. If the `item` parameter is omitted, |
731 |
* all options will be deselected. |
732 |
* |
733 |
* @param {OO.ui.OptionWidget} [item] Item to select, omit to deselect all |
734 |
* @fires select |
735 |
* @chainable |
736 |
*/ |
737 |
OO.ui.SelectWidget.prototype.selectItem = function ( item ) { |
738 |
var i, len, selected, |
739 |
changed = false; |
740 |
|
741 |
for ( i = 0, len = this.items.length; i < len; i++ ) { |
742 |
selected = this.items[ i ] === item; |
743 |
if ( this.items[ i ].isSelected() !== selected ) { |
744 |
this.items[ i ].setSelected( selected ); |
745 |
changed = true; |
746 |
} |
747 |
} |
748 |
if ( changed ) { |
749 |
if ( item && !item.constructor.static.highlightable ) { |
750 |
if ( item ) { |
751 |
this.$focusOwner.attr( 'aria-activedescendant', item.getElementId() ); |
752 |
} else { |
753 |
this.$focusOwner.removeAttr( 'aria-activedescendant' ); |
754 |
} |
755 |
} |
756 |
this.emit( 'select', item ); |
757 |
} |
758 |
|
759 |
return this; |
760 |
}; |
761 |
|
762 |
/** |
763 |
* Press an item. |
764 |
* |
765 |
* Press is a state that occurs when a user mouses down on an item, but has not |
766 |
* yet let go of the mouse. The item may appear selected, but it will not be selected until the user |
767 |
* releases the mouse. |
768 |
* |
769 |
* @param {OO.ui.OptionWidget} [item] Item to press, omit to depress all |
770 |
* @fires press |
771 |
* @chainable |
772 |
*/ |
773 |
OO.ui.SelectWidget.prototype.pressItem = function ( item ) { |
774 |
var i, len, pressed, |
775 |
changed = false; |
776 |
|
777 |
for ( i = 0, len = this.items.length; i < len; i++ ) { |
778 |
pressed = this.items[ i ] === item; |
779 |
if ( this.items[ i ].isPressed() !== pressed ) { |
780 |
this.items[ i ].setPressed( pressed ); |
781 |
changed = true; |
782 |
} |
783 |
} |
784 |
if ( changed ) { |
785 |
this.emit( 'press', item ); |
786 |
} |
787 |
|
788 |
return this; |
789 |
}; |
790 |
|
791 |
/** |
792 |
* Choose an item. |
793 |
* |
794 |
* Note that ‘choose’ should never be modified programmatically. A user can choose |
795 |
* an option with the keyboard or mouse and it becomes selected. To select an item programmatically, |
796 |
* use the #selectItem method. |
797 |
* |
798 |
* This method is identical to #selectItem, but may vary in subclasses that take additional action |
799 |
* when users choose an item with the keyboard or mouse. |
800 |
* |
801 |
* @param {OO.ui.OptionWidget} item Item to choose |
802 |
* @fires choose |
803 |
* @chainable |
804 |
*/ |
805 |
OO.ui.SelectWidget.prototype.chooseItem = function ( item ) { |
806 |
if ( item ) { |
807 |
this.selectItem( item ); |
808 |
this.emit( 'choose', item ); |
809 |
} |
810 |
|
811 |
return this; |
812 |
}; |
813 |
|
814 |
/** |
815 |
* Find an option by its position relative to the specified item (or to the start of the option array, |
816 |
* if item is `null`). The direction in which to search through the option array is specified with a |
817 |
* number: -1 for reverse (the default) or 1 for forward. The method will return an option, or |
818 |
* `null` if there are no options in the array. |
819 |
* |
820 |
* @param {OO.ui.OptionWidget|null} item Item to describe the start position, or `null` to start at the beginning of the array. |
821 |
* @param {number} direction Direction to move in: -1 to move backward, 1 to move forward |
822 |
* @param {Function} [filter] Only consider items for which this function returns |
823 |
* true. Function takes an OO.ui.OptionWidget and returns a boolean. |
824 |
* @return {OO.ui.OptionWidget|null} Item at position, `null` if there are no items in the select |
825 |
*/ |
826 |
OO.ui.SelectWidget.prototype.findRelativeSelectableItem = function ( item, direction, filter ) { |
827 |
var currentIndex, nextIndex, i, |
828 |
increase = direction > 0 ? 1 : -1, |
829 |
len = this.items.length; |
830 |
|
831 |
if ( item instanceof OO.ui.OptionWidget ) { |
832 |
currentIndex = this.items.indexOf( item ); |
833 |
nextIndex = ( currentIndex + increase + len ) % len; |
834 |
} else { |
835 |
// If no item is selected and moving forward, start at the beginning. |
836 |
// If moving backward, start at the end. |
837 |
nextIndex = direction > 0 ? 0 : len - 1; |
838 |
} |
839 |
|
840 |
for ( i = 0; i < len; i++ ) { |
841 |
item = this.items[ nextIndex ]; |
842 |
if ( |
843 |
item instanceof OO.ui.OptionWidget && item.isSelectable() && |
844 |
( !filter || filter( item ) ) |
845 |
) { |
846 |
return item; |
847 |
} |
848 |
nextIndex = ( nextIndex + increase + len ) % len; |
849 |
} |
850 |
return null; |
851 |
}; |
852 |
|
853 |
/** |
854 |
* Find the next selectable item or `null` if there are no selectable items. |
855 |
* Disabled options and menu-section markers and breaks are not selectable. |
856 |
* |
857 |
* @return {OO.ui.OptionWidget|null} Item, `null` if there aren't any selectable items |
858 |
*/ |
859 |
OO.ui.SelectWidget.prototype.findFirstSelectableItem = function () { |
860 |
return this.findRelativeSelectableItem( null, 1 ); |
861 |
}; |
862 |
|
863 |
/** |
864 |
* Add an array of options to the select. Optionally, an index number can be used to |
865 |
* specify an insertion point. |
866 |
* |
867 |
* @param {OO.ui.OptionWidget[]} items Items to add |
868 |
* @param {number} [index] Index to insert items after |
869 |
* @fires add |
870 |
* @chainable |
871 |
*/ |
872 |
OO.ui.SelectWidget.prototype.addItems = function ( items, index ) { |
873 |
// Mixin method |
874 |
OO.ui.mixin.GroupWidget.prototype.addItems.call( this, items, index ); |
875 |
|
876 |
// Always provide an index, even if it was omitted |
877 |
this.emit( 'add', items, index === undefined ? this.items.length - items.length - 1 : index ); |
878 |
|
879 |
return this; |
880 |
}; |
881 |
|
882 |
/** |
883 |
* Remove the specified array of options from the select. Options will be detached |
884 |
* from the DOM, not removed, so they can be reused later. To remove all options from |
885 |
* the select, you may wish to use the #clearItems method instead. |
886 |
* |
887 |
* @param {OO.ui.OptionWidget[]} items Items to remove |
888 |
* @fires remove |
889 |
* @chainable |
890 |
*/ |
891 |
OO.ui.SelectWidget.prototype.removeItems = function ( items ) { |
892 |
var i, len, item; |
893 |
|
894 |
// Deselect items being removed |
895 |
for ( i = 0, len = items.length; i < len; i++ ) { |
896 |
item = items[ i ]; |
897 |
if ( item.isSelected() ) { |
898 |
this.selectItem( null ); |
899 |
} |
900 |
} |
901 |
|
902 |
// Mixin method |
903 |
OO.ui.mixin.GroupWidget.prototype.removeItems.call( this, items ); |
904 |
|
905 |
this.emit( 'remove', items ); |
906 |
|
907 |
return this; |
908 |
}; |
909 |
|
910 |
/** |
911 |
* Clear all options from the select. Options will be detached from the DOM, not removed, |
912 |
* so that they can be reused later. To remove a subset of options from the select, use |
913 |
* the #removeItems method. |
914 |
* |
915 |
* @fires remove |
916 |
* @chainable |
917 |
*/ |
918 |
OO.ui.SelectWidget.prototype.clearItems = function () { |
919 |
var items = this.items.slice(); |
920 |
|
921 |
// Mixin method |
922 |
OO.ui.mixin.GroupWidget.prototype.clearItems.call( this ); |
923 |
|
924 |
// Clear selection |
925 |
this.selectItem( null ); |
926 |
|
927 |
this.emit( 'remove', items ); |
928 |
|
929 |
return this; |
930 |
}; |
931 |
|
932 |
/** |
933 |
* Set the DOM element which has focus while the user is interacting with this SelectWidget. |
934 |
* |
935 |
* Currently this is just used to set `aria-activedescendant` on it. |
936 |
* |
937 |
* @protected |
938 |
* @param {jQuery} $focusOwner |
939 |
*/ |
940 |
OO.ui.SelectWidget.prototype.setFocusOwner = function ( $focusOwner ) { |
941 |
this.$focusOwner = $focusOwner; |
942 |
}; |
943 |
|
|
|
/src/widgets/TabOptionWidget.js
|
0 problems
|
|
/src/widgets/TabSelectWidget.js
|
0 problems
|
|
/src/widgets/TagItemWidget.js
|
0 problems
|
|
/src/widgets/TagMultiselectWidget.js
|
3 problems (3 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 57, Column 15: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 90, Column 18: "Prefer classList to $.addClass"
jquery/no-class
|
Error |
Row 92, Column 17: "Prefer classList to $.addClass"
jquery/no-class
|
Line |
Source |
1 |
/** |
2 |
* A basic tag multiselect widget, similar in concept to {@link OO.ui.ComboBoxInputWidget combo box widget} |
3 |
* that allows the user to add multiple values that are displayed in a tag area. |
4 |
* |
5 |
* This widget is a base widget; see {@link OO.ui.MenuTagMultiselectWidget MenuTagMultiselectWidget} and |
6 |
* {@link OO.ui.PopupTagMultiselectWidget PopupTagMultiselectWidget} for the implementations that use |
7 |
* a menu and a popup respectively. |
8 |
* |
9 |
* @example |
10 |
* // Example: A basic TagMultiselectWidget. |
11 |
* var widget = new OO.ui.TagMultiselectWidget( { |
12 |
* inputPosition: 'outline', |
13 |
* allowedValues: [ 'Option 1', 'Option 2', 'Option 3' ], |
14 |
* selected: [ 'Option 1' ] |
15 |
* } ); |
16 |
* $( 'body' ).append( widget.$element ); |
17 |
* |
18 |
* @class |
19 |
* @extends OO.ui.Widget |
20 |
* @mixins OO.ui.mixin.GroupWidget |
21 |
* @mixins OO.ui.mixin.DraggableGroupElement |
22 |
* @mixins OO.ui.mixin.IndicatorElement |
23 |
* @mixins OO.ui.mixin.IconElement |
24 |
* @mixins OO.ui.mixin.TabIndexedElement |
25 |
* @mixins OO.ui.mixin.FlaggedElement |
26 |
* |
27 |
* @constructor |
28 |
* @param {Object} config Configuration object |
29 |
* @cfg {Object} [input] Configuration options for the input widget |
30 |
* @cfg {OO.ui.InputWidget} [inputWidget] An optional input widget. If given, it will |
31 |
* replace the input widget used in the TagMultiselectWidget. If not given, |
32 |
* TagMultiselectWidget creates its own. |
33 |
* @cfg {boolean} [inputPosition='inline'] Position of the input. Options are: |
34 |
* - inline: The input is invisible, but exists inside the tag list, so |
35 |
* the user types into the tag groups to add tags. |
36 |
* - outline: The input is underneath the tag area. |
37 |
* - none: No input supplied |
38 |
* @cfg {boolean} [allowEditTags=true] Allow editing of the tags by clicking them |
39 |
* @cfg {boolean} [allowArbitrary=false] Allow data items to be added even if |
40 |
* not present in the menu. |
41 |
* @cfg {Object[]} [allowedValues] An array representing the allowed items |
42 |
* by their datas. |
43 |
* @cfg {boolean} [allowDuplicates=false] Allow duplicate items to be added |
44 |
* @cfg {boolean} [allowDisplayInvalidTags=false] Allow the display of |
45 |
* invalid tags. These tags will display with an invalid state, and |
46 |
* the widget as a whole will have an invalid state if any invalid tags |
47 |
* are present. |
48 |
* @cfg {boolean} [allowReordering=true] Allow reordering of the items |
49 |
* @cfg {Object[]|String[]} [selected] A set of selected tags. If given, |
50 |
* these will appear in the tag list on initialization, as long as they |
51 |
* pass the validity tests. |
52 |
*/ |
53 |
OO.ui.TagMultiselectWidget = function OoUiTagMultiselectWidget( config ) { |
54 |
var inputEvents, |
55 |
rAF = window.requestAnimationFrame || setTimeout, |
56 |
widget = this, |
Error |
Row 57, Column 15: "Prefer classList to $.addClass"
jquery/no-class
|
57 |
$tabFocus = $( '<span>' ) |
58 |
.addClass( 'oo-ui-tagMultiselectWidget-focusTrap' ); |
59 |
|
60 |
config = config || {}; |
61 |
|
62 |
// Parent constructor |
63 |
OO.ui.TagMultiselectWidget.parent.call( this, config ); |
64 |
|
65 |
// Mixin constructors |
66 |
OO.ui.mixin.GroupWidget.call( this, config ); |
67 |
OO.ui.mixin.IndicatorElement.call( this, config ); |
68 |
OO.ui.mixin.IconElement.call( this, config ); |
69 |
OO.ui.mixin.TabIndexedElement.call( this, config ); |
70 |
OO.ui.mixin.FlaggedElement.call( this, config ); |
71 |
OO.ui.mixin.DraggableGroupElement.call( this, config ); |
72 |
|
73 |
this.toggleDraggable( |
74 |
config.allowReordering === undefined ? |
75 |
true : !!config.allowReordering |
76 |
); |
77 |
|
78 |
this.inputPosition = |
79 |
this.constructor.static.allowedInputPositions.indexOf( config.inputPosition ) > -1 ? |
80 |
config.inputPosition : 'inline'; |
81 |
this.allowEditTags = config.allowEditTags === undefined ? true : !!config.allowEditTags; |
82 |
this.allowArbitrary = !!config.allowArbitrary; |
83 |
this.allowDuplicates = !!config.allowDuplicates; |
84 |
this.allowedValues = config.allowedValues || []; |
85 |
this.allowDisplayInvalidTags = config.allowDisplayInvalidTags; |
86 |
this.hasInput = this.inputPosition !== 'none'; |
87 |
this.height = null; |
88 |
this.valid = true; |
89 |
|
Error |
Row 90, Column 18: "Prefer classList to $.addClass"
jquery/no-class
|
90 |
this.$content = $( '<div>' ) |
91 |
.addClass( 'oo-ui-tagMultiselectWidget-content' ); |
Error |
Row 92, Column 17: "Prefer classList to $.addClass"
jquery/no-class
|
92 |
this.$handle = $( '<div>' ) |
93 |
.addClass( 'oo-ui-tagMultiselectWidget-handle' ) |
94 |
.append( |
95 |
this.$indicator, |
96 |
this.$icon, |
97 |
this.$content |
98 |
.append( |
99 |
this.$group |
100 |
.addClass( 'oo-ui-tagMultiselectWidget-group' ) |
101 |
) |
102 |
); |
103 |
|
104 |
// Events |
105 |
this.aggregate( { |
106 |
remove: 'itemRemove', |
107 |
navigate: 'itemNavigate', |
108 |
select: 'itemSelect', |
109 |
fixed: 'itemFixed' |
110 |
} ); |
111 |
this.connect( this, { |
112 |
itemRemove: 'onTagRemove', |
113 |
itemSelect: 'onTagSelect', |
114 |
itemFixed: 'onTagFixed', |
115 |
itemNavigate: 'onTagNavigate', |
116 |
change: 'onChangeTags' |
117 |
} ); |
118 |
this.$handle.on( { |
119 |
mousedown: this.onMouseDown.bind( this ) |
120 |
} ); |
121 |
|
122 |
// Initialize |
123 |
this.$element |
124 |
.addClass( 'oo-ui-tagMultiselectWidget' ) |
125 |
.append( this.$handle ); |
126 |
|
127 |
if ( this.hasInput ) { |
128 |
if ( config.inputWidget ) { |
129 |
this.input = config.inputWidget; |
130 |
} else { |
131 |
this.input = new OO.ui.TextInputWidget( $.extend( { |
132 |
placeholder: config.placeholder, |
133 |
classes: [ 'oo-ui-tagMultiselectWidget-input' ] |
134 |
}, config.input ) ); |
135 |
} |
136 |
this.input.setDisabled( this.isDisabled() ); |
137 |
|
138 |
inputEvents = { |
139 |
focus: this.onInputFocus.bind( this ), |
140 |
blur: this.onInputBlur.bind( this ), |
141 |
'propertychange change click mouseup keydown keyup input cut paste select focus': |
142 |
OO.ui.debounce( this.updateInputSize.bind( this ) ), |
143 |
keydown: this.onInputKeyDown.bind( this ), |
144 |
keypress: this.onInputKeyPress.bind( this ) |
145 |
}; |
146 |
|
147 |
this.input.$input.on( inputEvents ); |
148 |
|
149 |
if ( this.inputPosition === 'outline' ) { |
150 |
// Override max-height for the input widget |
151 |
// in the case the widget is outline so it can |
152 |
// stretch all the way if the widget is wide |
153 |
this.input.$element.css( 'max-width', 'inherit' ); |
154 |
this.$element |
155 |
.addClass( 'oo-ui-tagMultiselectWidget-outlined' ) |
156 |
.append( this.input.$element ); |
157 |
} else { |
158 |
this.$element.addClass( 'oo-ui-tagMultiselectWidget-inlined' ); |
159 |
// HACK: When the widget is using 'inline' input, the |
160 |
// behavior needs to only use the $input itself |
161 |
// so we style and size it accordingly (otherwise |
162 |
// the styling and sizing can get very convoluted |
163 |
// when the wrapping divs and other elements) |
164 |
// We are taking advantage of still being able to |
165 |
// call the widget itself for operations like |
166 |
// .getValue() and setDisabled() and .focus() but |
167 |
// having only the $input attached to the DOM |
168 |
this.$content.append( this.input.$input ); |
169 |
} |
170 |
} else { |
171 |
this.$content.append( $tabFocus ); |
172 |
} |
173 |
|
174 |
this.setTabIndexedElement( |
175 |
this.hasInput ? |
176 |
this.input.$input : |
177 |
$tabFocus |
178 |
); |
179 |
|
180 |
if ( config.selected ) { |
181 |
this.setValue( config.selected ); |
182 |
} |
183 |
|
184 |
// HACK: Input size needs to be calculated after everything |
185 |
// else is rendered |
186 |
rAF( function () { |
187 |
if ( widget.hasInput ) { |
188 |
widget.updateInputSize(); |
189 |
} |
190 |
} ); |
191 |
}; |
192 |
|
193 |
/* Initialization */ |
194 |
|
195 |
OO.inheritClass( OO.ui.TagMultiselectWidget, OO.ui.Widget ); |
196 |
OO.mixinClass( OO.ui.TagMultiselectWidget, OO.ui.mixin.GroupWidget ); |
197 |
OO.mixinClass( OO.ui.TagMultiselectWidget, OO.ui.mixin.DraggableGroupElement ); |
198 |
OO.mixinClass( OO.ui.TagMultiselectWidget, OO.ui.mixin.IndicatorElement ); |
199 |
OO.mixinClass( OO.ui.TagMultiselectWidget, OO.ui.mixin.IconElement ); |
200 |
OO.mixinClass( OO.ui.TagMultiselectWidget, OO.ui.mixin.TabIndexedElement ); |
201 |
OO.mixinClass( OO.ui.TagMultiselectWidget, OO.ui.mixin.FlaggedElement ); |
202 |
|
203 |
/* Static properties */ |
204 |
|
205 |
/** |
206 |
* Allowed input positions. |
207 |
* - inline: The input is inside the tag list |
208 |
* - outline: The input is under the tag list |
209 |
* - none: There is no input |
210 |
* |
211 |
* @property {Array} |
212 |
*/ |
213 |
OO.ui.TagMultiselectWidget.static.allowedInputPositions = [ 'inline', 'outline', 'none' ]; |
214 |
|
215 |
/* Methods */ |
216 |
|
217 |
/** |
218 |
* Handle mouse down events. |
219 |
* |
220 |
* @private |
221 |
* @param {jQuery.Event} e Mouse down event |
222 |
* @return {boolean} False to prevent defaults |
223 |
*/ |
224 |
OO.ui.TagMultiselectWidget.prototype.onMouseDown = function ( e ) { |
225 |
if ( |
226 |
!this.isDisabled() && |
227 |
( !this.hasInput || e.target !== this.input.$input[ 0 ] ) && |
228 |
e.which === OO.ui.MouseButtons.LEFT |
229 |
) { |
230 |
this.focus(); |
231 |
return false; |
232 |
} |
233 |
}; |
234 |
|
235 |
/** |
236 |
* Handle key press events. |
237 |
* |
238 |
* @private |
239 |
* @param {jQuery.Event} e Key press event |
240 |
* @return {boolean} Whether to prevent defaults |
241 |
*/ |
242 |
OO.ui.TagMultiselectWidget.prototype.onInputKeyPress = function ( e ) { |
243 |
var stopOrContinue, |
244 |
withMetaKey = e.metaKey || e.ctrlKey; |
245 |
|
246 |
if ( !this.isDisabled() ) { |
247 |
if ( e.which === OO.ui.Keys.ENTER ) { |
248 |
stopOrContinue = this.doInputEnter( e, withMetaKey ); |
249 |
} |
250 |
|
251 |
// Make sure the input gets resized. |
252 |
setTimeout( this.updateInputSize.bind( this ), 0 ); |
253 |
return stopOrContinue; |
254 |
} |
255 |
}; |
256 |
|
257 |
/** |
258 |
* Handle key down events. |
259 |
* |
260 |
* @private |
261 |
* @param {jQuery.Event} e Key down event |
262 |
* @return {boolean} |
263 |
*/ |
264 |
OO.ui.TagMultiselectWidget.prototype.onInputKeyDown = function ( e ) { |
265 |
var movement, direction, |
266 |
widget = this, |
267 |
withMetaKey = e.metaKey || e.ctrlKey, |
268 |
isMovementInsideInput = function ( direction ) { |
269 |
var inputRange = widget.input.getRange(), |
270 |
inputValue = widget.hasInput && widget.input.getValue(); |
271 |
|
272 |
if ( direction === 'forwards' && inputRange.to > inputValue.length - 1 ) { |
273 |
return false; |
274 |
} |
275 |
|
276 |
if ( direction === 'backwards' && inputRange.from <= 0 ) { |
277 |
return false; |
278 |
} |
279 |
|
280 |
return true; |
281 |
}; |
282 |
|
283 |
if ( !this.isDisabled() ) { |
284 |
// 'keypress' event is not triggered for Backspace |
285 |
if ( e.keyCode === OO.ui.Keys.BACKSPACE ) { |
286 |
return this.doInputBackspace( e, withMetaKey ); |
287 |
} else if ( e.keyCode === OO.ui.Keys.ESCAPE ) { |
288 |
return this.doInputEscape( e ); |
289 |
} else if ( |
290 |
e.keyCode === OO.ui.Keys.LEFT || |
291 |
e.keyCode === OO.ui.Keys.RIGHT |
292 |
) { |
293 |
if ( OO.ui.Element.static.getDir( this.$element ) === 'rtl' ) { |
294 |
movement = { |
295 |
left: 'forwards', |
296 |
right: 'backwards' |
297 |
}; |
298 |
} else { |
299 |
movement = { |
300 |
left: 'backwards', |
301 |
right: 'forwards' |
302 |
}; |
303 |
} |
304 |
direction = e.keyCode === OO.ui.Keys.LEFT ? |
305 |
movement.left : movement.right; |
306 |
|
307 |
if ( !this.hasInput || !isMovementInsideInput( direction ) ) { |
308 |
return this.doInputArrow( e, direction, withMetaKey ); |
309 |
} |
310 |
} |
311 |
} |
312 |
}; |
313 |
|
314 |
/** |
315 |
* Respond to input focus event |
316 |
*/ |
317 |
OO.ui.TagMultiselectWidget.prototype.onInputFocus = function () { |
318 |
this.$element.addClass( 'oo-ui-tagMultiselectWidget-focus' ); |
319 |
}; |
320 |
|
321 |
/** |
322 |
* Respond to input blur event |
323 |
*/ |
324 |
OO.ui.TagMultiselectWidget.prototype.onInputBlur = function () { |
325 |
this.$element.removeClass( 'oo-ui-tagMultiselectWidget-focus' ); |
326 |
}; |
327 |
|
328 |
/** |
329 |
* Perform an action after the enter key on the input |
330 |
* |
331 |
* @param {jQuery.Event} e Event data |
332 |
* @param {boolean} [withMetaKey] Whether this key was pressed with |
333 |
* a meta key like 'ctrl' |
334 |
* @return {boolean} Whether to prevent defaults |
335 |
*/ |
336 |
OO.ui.TagMultiselectWidget.prototype.doInputEnter = function () { |
337 |
this.addTagFromInput(); |
338 |
return false; |
339 |
}; |
340 |
|
341 |
/** |
342 |
* Perform an action responding to the enter key on the input |
343 |
* |
344 |
* @param {jQuery.Event} e Event data |
345 |
* @param {boolean} [withMetaKey] Whether this key was pressed with |
346 |
* a meta key like 'ctrl' |
347 |
* @return {boolean} Whether to prevent defaults |
348 |
*/ |
349 |
OO.ui.TagMultiselectWidget.prototype.doInputBackspace = function ( e, withMetaKey ) { |
350 |
var items, item; |
351 |
|
352 |
if ( |
353 |
this.inputPosition === 'inline' && |
354 |
this.input.getValue() === '' && |
355 |
!this.isEmpty() |
356 |
) { |
357 |
// Delete the last item |
358 |
items = this.getItems(); |
359 |
item = items[ items.length - 1 ]; |
360 |
|
361 |
if ( !item.isDisabled() && !item.isFixed() ) { |
362 |
this.removeItems( [ item ] ); |
363 |
// If Ctrl/Cmd was pressed, delete item entirely. |
364 |
// Otherwise put it into the text field for editing. |
365 |
if ( !withMetaKey ) { |
366 |
this.input.setValue( item.getData() ); |
367 |
} |
368 |
} |
369 |
|
370 |
return false; |
371 |
} |
372 |
}; |
373 |
|
374 |
/** |
375 |
* Perform an action after the escape key on the input |
376 |
* |
377 |
* @param {jQuery.Event} e Event data |
378 |
*/ |
379 |
OO.ui.TagMultiselectWidget.prototype.doInputEscape = function () { |
380 |
this.clearInput(); |
381 |
}; |
382 |
|
383 |
/** |
384 |
* Perform an action after the arrow key on the input, select the previous |
385 |
* item from the input. |
386 |
* See #getPreviousItem |
387 |
* |
388 |
* @param {jQuery.Event} e Event data |
389 |
* @param {string} direction Direction of the movement; forwards or backwards |
390 |
* @param {boolean} [withMetaKey] Whether this key was pressed with |
391 |
* a meta key like 'ctrl' |
392 |
*/ |
393 |
OO.ui.TagMultiselectWidget.prototype.doInputArrow = function ( e, direction ) { |
394 |
if ( |
395 |
this.inputPosition === 'inline' && |
396 |
!this.isEmpty() && |
397 |
direction === 'backwards' |
398 |
) { |
399 |
// Get previous item |
400 |
this.getPreviousItem().focus(); |
401 |
} |
402 |
}; |
403 |
|
404 |
/** |
405 |
* Respond to item select event |
406 |
* |
407 |
* @param {OO.ui.TagItemWidget} item Selected item |
408 |
*/ |
409 |
OO.ui.TagMultiselectWidget.prototype.onTagSelect = function ( item ) { |
410 |
if ( this.hasInput && this.allowEditTags && !item.isFixed() ) { |
411 |
if ( this.input.getValue() ) { |
412 |
this.addTagFromInput(); |
413 |
} |
414 |
// 1. Get the label of the tag into the input |
415 |
this.input.setValue( item.getData() ); |
416 |
// 2. Remove the tag |
417 |
this.removeItems( [ item ] ); |
418 |
// 3. Focus the input |
419 |
this.focus(); |
420 |
} |
421 |
}; |
422 |
|
423 |
/** |
424 |
* Respond to item fixed state change |
425 |
* |
426 |
* @param {OO.ui.TagItemWidget} item Selected item |
427 |
*/ |
428 |
OO.ui.TagMultiselectWidget.prototype.onTagFixed = function ( item ) { |
429 |
var i, |
430 |
items = this.getItems(); |
431 |
|
432 |
// Move item to the end of the static items |
433 |
for ( i = 0; i < items.length; i++ ) { |
434 |
if ( items[ i ] !== item && !items[ i ].isFixed() ) { |
435 |
break; |
436 |
} |
437 |
} |
438 |
this.addItems( item, i ); |
439 |
}; |
440 |
/** |
441 |
* Respond to change event, where items were added, removed, or cleared. |
442 |
*/ |
443 |
OO.ui.TagMultiselectWidget.prototype.onChangeTags = function () { |
444 |
this.toggleValid( this.checkValidity() ); |
445 |
if ( this.hasInput ) { |
446 |
this.updateInputSize(); |
447 |
} |
448 |
this.updateIfHeightChanged(); |
449 |
}; |
450 |
|
451 |
/** |
452 |
* @inheritdoc |
453 |
*/ |
454 |
OO.ui.TagMultiselectWidget.prototype.setDisabled = function ( isDisabled ) { |
455 |
// Parent method |
456 |
OO.ui.TagMultiselectWidget.parent.prototype.setDisabled.call( this, isDisabled ); |
457 |
|
458 |
if ( this.hasInput && this.input ) { |
459 |
this.input.setDisabled( !!isDisabled ); |
460 |
} |
461 |
|
462 |
if ( this.items ) { |
463 |
this.getItems().forEach( function ( item ) { |
464 |
item.setDisabled( !!isDisabled ); |
465 |
} ); |
466 |
} |
467 |
}; |
468 |
|
469 |
/** |
470 |
* Respond to tag remove event |
471 |
* @param {OO.ui.TagItemWidget} item Removed tag |
472 |
*/ |
473 |
OO.ui.TagMultiselectWidget.prototype.onTagRemove = function ( item ) { |
474 |
this.removeTagByData( item.getData() ); |
475 |
}; |
476 |
|
477 |
/** |
478 |
* Respond to navigate event on the tag |
479 |
* |
480 |
* @param {OO.ui.TagItemWidget} item Removed tag |
481 |
* @param {string} direction Direction of movement; 'forwards' or 'backwards' |
482 |
*/ |
483 |
OO.ui.TagMultiselectWidget.prototype.onTagNavigate = function ( item, direction ) { |
484 |
var firstItem = this.getItems()[ 0 ]; |
485 |
|
486 |
if ( direction === 'forwards' ) { |
487 |
this.getNextItem( item ).focus(); |
488 |
} else if ( !this.inputPosition === 'inline' || item !== firstItem ) { |
489 |
// If the widget has an inline input, we want to stop at the starting edge |
490 |
// of the tags |
491 |
this.getPreviousItem( item ).focus(); |
492 |
} |
493 |
}; |
494 |
|
495 |
/** |
496 |
* Add tag from input value |
497 |
*/ |
498 |
OO.ui.TagMultiselectWidget.prototype.addTagFromInput = function () { |
499 |
var val = this.input.getValue(), |
500 |
isValid = this.isAllowedData( val ); |
501 |
|
502 |
if ( !val ) { |
503 |
return; |
504 |
} |
505 |
|
506 |
if ( isValid || this.allowDisplayInvalidTags ) { |
507 |
this.addTag( val ); |
508 |
this.clearInput(); |
509 |
this.focus(); |
510 |
} |
511 |
}; |
512 |
|
513 |
/** |
514 |
* Clear the input |
515 |
*/ |
516 |
OO.ui.TagMultiselectWidget.prototype.clearInput = function () { |
517 |
this.input.setValue( '' ); |
518 |
}; |
519 |
|
520 |
/** |
521 |
* Check whether the given value is a duplicate of an existing |
522 |
* tag already in the list. |
523 |
* |
524 |
* @param {string|Object} data Requested value |
525 |
* @return {boolean} Value is duplicate |
526 |
*/ |
527 |
OO.ui.TagMultiselectWidget.prototype.isDuplicateData = function ( data ) { |
528 |
return !!this.findItemFromData( data ); |
529 |
}; |
530 |
|
531 |
/** |
532 |
* Check whether a given value is allowed to be added |
533 |
* |
534 |
* @param {string|Object} data Requested value |
535 |
* @return {boolean} Value is allowed |
536 |
*/ |
537 |
OO.ui.TagMultiselectWidget.prototype.isAllowedData = function ( data ) { |
538 |
if ( |
539 |
!this.allowDuplicates && |
540 |
this.isDuplicateData( data ) |
541 |
) { |
542 |
return false; |
543 |
} |
544 |
|
545 |
if ( this.allowArbitrary ) { |
546 |
return true; |
547 |
} |
548 |
|
549 |
// Check with allowed values |
550 |
if ( |
551 |
this.getAllowedValues().some( function ( value ) { |
552 |
return data === value; |
553 |
} ) |
554 |
) { |
555 |
return true; |
556 |
} |
557 |
|
558 |
return false; |
559 |
}; |
560 |
|
561 |
/** |
562 |
* Get the allowed values list |
563 |
* |
564 |
* @return {string[]} Allowed data values |
565 |
*/ |
566 |
OO.ui.TagMultiselectWidget.prototype.getAllowedValues = function () { |
567 |
return this.allowedValues; |
568 |
}; |
569 |
|
570 |
/** |
571 |
* Add a value to the allowed values list |
572 |
* |
573 |
* @param {string} value Allowed data value |
574 |
*/ |
575 |
OO.ui.TagMultiselectWidget.prototype.addAllowedValue = function ( value ) { |
576 |
if ( this.allowedValues.indexOf( value ) === -1 ) { |
577 |
this.allowedValues.push( value ); |
578 |
} |
579 |
}; |
580 |
|
581 |
/** |
582 |
* Get the datas of the currently selected items |
583 |
* |
584 |
* @return {string[]|Object[]} Datas of currently selected items |
585 |
*/ |
586 |
OO.ui.TagMultiselectWidget.prototype.getValue = function () { |
587 |
return this.getItems() |
588 |
.filter( function ( item ) { |
589 |
return item.isValid(); |
590 |
} ) |
591 |
.map( function ( item ) { |
592 |
return item.getData(); |
593 |
} ); |
594 |
}; |
595 |
|
596 |
/** |
597 |
* Set the value of this widget by datas. |
598 |
* |
599 |
* @param {string|string[]|Object|Object[]} valueObject An object representing the data |
600 |
* and label of the value. If the widget allows arbitrary values, |
601 |
* the items will be added as-is. Otherwise, the data value will |
602 |
* be checked against allowedValues. |
603 |
* This object must contain at least a data key. Example: |
604 |
* { data: 'foo', label: 'Foo item' } |
605 |
* For multiple items, use an array of objects. For example: |
606 |
* [ |
607 |
* { data: 'foo', label: 'Foo item' }, |
608 |
* { data: 'bar', label: 'Bar item' } |
609 |
* ] |
610 |
* Value can also be added with plaintext array, for example: |
611 |
* [ 'foo', 'bar', 'bla' ] or a single string, like 'foo' |
612 |
*/ |
613 |
OO.ui.TagMultiselectWidget.prototype.setValue = function ( valueObject ) { |
614 |
valueObject = Array.isArray( valueObject ) ? valueObject : [ valueObject ]; |
615 |
|
616 |
this.clearItems(); |
617 |
valueObject.forEach( function ( obj ) { |
618 |
if ( typeof obj === 'string' ) { |
619 |
this.addTag( obj ); |
620 |
} else { |
621 |
this.addTag( obj.data, obj.label ); |
622 |
} |
623 |
}.bind( this ) ); |
624 |
}; |
625 |
|
626 |
/** |
627 |
* Add tag to the display area |
628 |
* |
629 |
* @param {string|Object} data Tag data |
630 |
* @param {string} [label] Tag label. If no label is provided, the |
631 |
* stringified version of the data will be used instead. |
632 |
* @return {boolean} Item was added successfully |
633 |
*/ |
634 |
OO.ui.TagMultiselectWidget.prototype.addTag = function ( data, label ) { |
635 |
var newItemWidget, |
636 |
isValid = this.isAllowedData( data ); |
637 |
|
638 |
if ( isValid || this.allowDisplayInvalidTags ) { |
639 |
newItemWidget = this.createTagItemWidget( data, label ); |
640 |
newItemWidget.toggleValid( isValid ); |
641 |
this.addItems( [ newItemWidget ] ); |
642 |
return true; |
643 |
} |
644 |
return false; |
645 |
}; |
646 |
|
647 |
/** |
648 |
* Remove tag by its data property. |
649 |
* |
650 |
* @param {string|Object} data Tag data |
651 |
*/ |
652 |
OO.ui.TagMultiselectWidget.prototype.removeTagByData = function ( data ) { |
653 |
var item = this.findItemFromData( data ); |
654 |
|
655 |
this.removeItems( [ item ] ); |
656 |
}; |
657 |
|
658 |
/** |
659 |
* Construct a OO.ui.TagItemWidget (or a subclass thereof) from given label and data. |
660 |
* |
661 |
* @protected |
662 |
* @param {string} data Item data |
663 |
* @param {string} label The label text. |
664 |
* @return {OO.ui.TagItemWidget} |
665 |
*/ |
666 |
OO.ui.TagMultiselectWidget.prototype.createTagItemWidget = function ( data, label ) { |
667 |
label = label || data; |
668 |
|
669 |
return new OO.ui.TagItemWidget( { data: data, label: label } ); |
670 |
}; |
671 |
|
672 |
/** |
673 |
* Given an item, returns the item after it. If the item is already the |
674 |
* last item, return `this.input`. If no item is passed, returns the |
675 |
* very first item. |
676 |
* |
677 |
* @protected |
678 |
* @param {OO.ui.TagItemWidget} [item] Tag item |
679 |
* @return {OO.ui.Widget} The next widget available. |
680 |
*/ |
681 |
OO.ui.TagMultiselectWidget.prototype.getNextItem = function ( item ) { |
682 |
var itemIndex = this.items.indexOf( item ); |
683 |
|
684 |
if ( item === undefined || itemIndex === -1 ) { |
685 |
return this.items[ 0 ]; |
686 |
} |
687 |
|
688 |
if ( itemIndex === this.items.length - 1 ) { // Last item |
689 |
if ( this.hasInput ) { |
690 |
return this.input; |
691 |
} else { |
692 |
// Return first item |
693 |
return this.items[ 0 ]; |
694 |
} |
695 |
} else { |
696 |
return this.items[ itemIndex + 1 ]; |
697 |
} |
698 |
}; |
699 |
|
700 |
/** |
701 |
* Given an item, returns the item before it. If the item is already the |
702 |
* first item, return `this.input`. If no item is passed, returns the |
703 |
* very last item. |
704 |
* |
705 |
* @protected |
706 |
* @param {OO.ui.TagItemWidget} [item] Tag item |
707 |
* @return {OO.ui.Widget} The previous widget available. |
708 |
*/ |
709 |
OO.ui.TagMultiselectWidget.prototype.getPreviousItem = function ( item ) { |
710 |
var itemIndex = this.items.indexOf( item ); |
711 |
|
712 |
if ( item === undefined || itemIndex === -1 ) { |
713 |
return this.items[ this.items.length - 1 ]; |
714 |
} |
715 |
|
716 |
if ( itemIndex === 0 ) { |
717 |
if ( this.hasInput ) { |
718 |
return this.input; |
719 |
} else { |
720 |
// Return the last item |
721 |
return this.items[ this.items.length - 1 ]; |
722 |
} |
723 |
} else { |
724 |
return this.items[ itemIndex - 1 ]; |
725 |
} |
726 |
}; |
727 |
|
728 |
/** |
729 |
* Update the dimensions of the text input field to encompass all available area. |
730 |
* This is especially relevant for when the input is at the edge of a line |
731 |
* and should get smaller. The usual operation (as an inline-block with min-width) |
732 |
* does not work in that case, pushing the input downwards to the next line. |
733 |
* |
734 |
* @private |
735 |
*/ |
736 |
OO.ui.TagMultiselectWidget.prototype.updateInputSize = function () { |
737 |
var $lastItem, direction, contentWidth, currentWidth, bestWidth; |
738 |
if ( this.inputPosition === 'inline' && !this.isDisabled() ) { |
739 |
if ( this.input.$input[ 0 ].scrollWidth === 0 ) { |
740 |
// Input appears to be attached but not visible. |
741 |
// Don't attempt to adjust its size, because our measurements |
742 |
// are going to fail anyway. |
743 |
return; |
744 |
} |
745 |
this.input.$input.css( 'width', '1em' ); |
746 |
$lastItem = this.$group.children().last(); |
747 |
direction = OO.ui.Element.static.getDir( this.$handle ); |
748 |
|
749 |
// Get the width of the input with the placeholder text as |
750 |
// the value and save it so that we don't keep recalculating |
751 |
if ( |
752 |
this.contentWidthWithPlaceholder === undefined && |
753 |
this.input.getValue() === '' && |
754 |
this.input.$input.attr( 'placeholder' ) !== undefined |
755 |
) { |
756 |
this.input.setValue( this.input.$input.attr( 'placeholder' ) ); |
757 |
this.contentWidthWithPlaceholder = this.input.$input[ 0 ].scrollWidth; |
758 |
this.input.setValue( '' ); |
759 |
|
760 |
} |
761 |
|
762 |
// Always keep the input wide enough for the placeholder text |
763 |
contentWidth = Math.max( |
764 |
this.input.$input[ 0 ].scrollWidth, |
765 |
// undefined arguments in Math.max lead to NaN |
766 |
( this.contentWidthWithPlaceholder === undefined ) ? |
767 |
0 : this.contentWidthWithPlaceholder |
768 |
); |
769 |
currentWidth = this.input.$input.width(); |
770 |
|
771 |
if ( contentWidth < currentWidth ) { |
772 |
this.updateIfHeightChanged(); |
773 |
// All is fine, don't perform expensive calculations |
774 |
return; |
775 |
} |
776 |
|
777 |
if ( $lastItem.length === 0 ) { |
778 |
bestWidth = this.$content.innerWidth(); |
779 |
} else { |
780 |
bestWidth = direction === 'ltr' ? |
781 |
this.$content.innerWidth() - $lastItem.position().left - $lastItem.outerWidth() : |
782 |
$lastItem.position().left; |
783 |
} |
784 |
|
785 |
// Some safety margin for sanity, because I *really* don't feel like finding out where the few |
786 |
// pixels this is off by are coming from. |
787 |
bestWidth -= 10; |
788 |
if ( contentWidth > bestWidth ) { |
789 |
// This will result in the input getting shifted to the next line |
790 |
bestWidth = this.$content.innerWidth() - 10; |
791 |
} |
792 |
this.input.$input.width( Math.floor( bestWidth ) ); |
793 |
this.updateIfHeightChanged(); |
794 |
} else { |
795 |
this.updateIfHeightChanged(); |
796 |
} |
797 |
}; |
798 |
|
799 |
/** |
800 |
* Determine if widget height changed, and if so, |
801 |
* emit the resize event. This is useful for when there are either |
802 |
* menus or popups attached to the bottom of the widget, to allow |
803 |
* them to change their positioning in case the widget moved down |
804 |
* or up. |
805 |
* |
806 |
* @private |
807 |
*/ |
808 |
OO.ui.TagMultiselectWidget.prototype.updateIfHeightChanged = function () { |
809 |
var height = this.$element.height(); |
810 |
if ( height !== this.height ) { |
811 |
this.height = height; |
812 |
this.emit( 'resize' ); |
813 |
} |
814 |
}; |
815 |
|
816 |
/** |
817 |
* Check whether all items in the widget are valid |
818 |
* |
819 |
* @return {boolean} Widget is valid |
820 |
*/ |
821 |
OO.ui.TagMultiselectWidget.prototype.checkValidity = function () { |
822 |
return this.getItems().every( function ( item ) { |
823 |
return item.isValid(); |
824 |
} ); |
825 |
}; |
826 |
|
827 |
/** |
828 |
* Set the valid state of this item |
829 |
* |
830 |
* @param {boolean} [valid] Item is valid |
831 |
* @fires valid |
832 |
*/ |
833 |
OO.ui.TagMultiselectWidget.prototype.toggleValid = function ( valid ) { |
834 |
valid = valid === undefined ? !this.valid : !!valid; |
835 |
|
836 |
if ( this.valid !== valid ) { |
837 |
this.valid = valid; |
838 |
|
839 |
this.setFlags( { invalid: !this.valid } ); |
840 |
|
841 |
this.emit( 'valid', this.valid ); |
842 |
} |
843 |
}; |
844 |
|
845 |
/** |
846 |
* Get the current valid state of the widget |
847 |
* |
848 |
* @return {boolean} Widget is valid |
849 |
*/ |
850 |
OO.ui.TagMultiselectWidget.prototype.isValid = function () { |
851 |
return this.valid; |
852 |
}; |
853 |
|
|
|
/src/widgets/TextInputWidget.js
|
5 problems (5 errors, 0 warnings)
|
Line |
Source |
1 |
/** |
2 |
* TextInputWidgets, like HTML text inputs, can be configured with options that customize the |
3 |
* size of the field as well as its presentation. In addition, these widgets can be configured |
4 |
* with {@link OO.ui.mixin.IconElement icons}, {@link OO.ui.mixin.IndicatorElement indicators}, an optional |
5 |
* validation-pattern (used to determine if an input value is valid or not) and an input filter, |
6 |
* which modifies incoming values rather than validating them. |
7 |
* Please see the [OOUI documentation on MediaWiki] [1] for more information and examples. |
8 |
* |
9 |
* This widget can be used inside an HTML form, such as a OO.ui.FormLayout. |
10 |
* |
11 |
* @example |
12 |
* // Example of a text input widget |
13 |
* var textInput = new OO.ui.TextInputWidget( { |
14 |
* value: 'Text input' |
15 |
* } ) |
16 |
* $( 'body' ).append( textInput.$element ); |
17 |
* |
18 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs |
19 |
* |
20 |
* @class |
21 |
* @extends OO.ui.InputWidget |
22 |
* @mixins OO.ui.mixin.IconElement |
23 |
* @mixins OO.ui.mixin.IndicatorElement |
24 |
* @mixins OO.ui.mixin.PendingElement |
25 |
* @mixins OO.ui.mixin.LabelElement |
26 |
* |
27 |
* @constructor |
28 |
* @param {Object} [config] Configuration options |
29 |
* @cfg {string} [type='text'] The value of the HTML `type` attribute: 'text', 'password' |
30 |
* 'email', 'url' or 'number'. |
31 |
* @cfg {string} [placeholder] Placeholder text |
32 |
* @cfg {boolean} [autofocus=false] Use an HTML `autofocus` attribute to |
33 |
* instruct the browser to focus this widget. |
34 |
* @cfg {boolean} [readOnly=false] Prevent changes to the value of the text input. |
35 |
* @cfg {number} [maxLength] Maximum number of characters allowed in the input. |
36 |
* |
37 |
* For unfortunate historical reasons, this counts the number of UTF-16 code units rather than |
38 |
* Unicode codepoints, which means that codepoints outside the Basic Multilingual Plane (e.g. |
39 |
* many emojis) count as 2 characters each. |
40 |
* @cfg {string} [labelPosition='after'] The position of the inline label relative to that of |
41 |
* the value or placeholder text: `'before'` or `'after'` |
42 |
* @cfg {boolean} [required=false] Mark the field as required with `true`. Implies `indicator: 'required'`. |
43 |
* Note that `false` & setting `indicator: 'required' will result in no indicator shown. |
44 |
* @cfg {boolean} [autocomplete=true] Should the browser support autocomplete for this field |
45 |
* @cfg {boolean} [spellcheck] Should the browser support spellcheck for this field (`undefined` means |
46 |
* leaving it up to the browser). |
47 |
* @cfg {RegExp|Function|string} [validate] Validation pattern: when string, a symbolic name of a |
48 |
* pattern defined by the class: 'non-empty' (the value cannot be an empty string) or 'integer' |
49 |
* (the value must contain only numbers); when RegExp, a regular expression that must match the |
50 |
* value for it to be considered valid; when Function, a function receiving the value as parameter |
51 |
* that must return true, or promise resolving to true, for it to be considered valid. |
52 |
*/ |
53 |
OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) { |
54 |
// Configuration initialization |
55 |
config = $.extend( { |
56 |
type: 'text', |
57 |
labelPosition: 'after' |
58 |
}, config ); |
59 |
|
60 |
// Parent constructor |
61 |
OO.ui.TextInputWidget.parent.call( this, config ); |
62 |
|
63 |
// Mixin constructors |
64 |
OO.ui.mixin.IconElement.call( this, config ); |
65 |
OO.ui.mixin.IndicatorElement.call( this, config ); |
66 |
OO.ui.mixin.PendingElement.call( this, $.extend( {}, config, { $pending: this.$input } ) ); |
67 |
OO.ui.mixin.LabelElement.call( this, config ); |
68 |
|
69 |
// Properties |
70 |
this.type = this.getSaneType( config ); |
71 |
this.readOnly = false; |
72 |
this.required = false; |
73 |
this.validate = null; |
74 |
this.styleHeight = null; |
75 |
this.scrollWidth = null; |
76 |
|
77 |
this.setValidation( config.validate ); |
78 |
this.setLabelPosition( config.labelPosition ); |
79 |
|
80 |
// Events |
81 |
this.$input.on( { |
82 |
keypress: this.onKeyPress.bind( this ), |
83 |
blur: this.onBlur.bind( this ), |
84 |
focus: this.onFocus.bind( this ) |
85 |
} ); |
86 |
this.$icon.on( 'mousedown', this.onIconMouseDown.bind( this ) ); |
87 |
this.$indicator.on( 'mousedown', this.onIndicatorMouseDown.bind( this ) ); |
88 |
this.on( 'labelChange', this.updatePosition.bind( this ) ); |
89 |
this.on( 'change', OO.ui.debounce( this.onDebouncedChange.bind( this ), 250 ) ); |
90 |
|
91 |
// Initialization |
92 |
this.$element |
93 |
.addClass( 'oo-ui-textInputWidget oo-ui-textInputWidget-type-' + this.type ) |
94 |
.append( this.$icon, this.$indicator ); |
95 |
this.setReadOnly( !!config.readOnly ); |
96 |
this.setRequired( !!config.required ); |
97 |
if ( config.placeholder !== undefined ) { |
98 |
this.$input.attr( 'placeholder', config.placeholder ); |
99 |
} |
100 |
if ( config.maxLength !== undefined ) { |
101 |
this.$input.attr( 'maxlength', config.maxLength ); |
102 |
} |
103 |
if ( config.autofocus ) { |
104 |
this.$input.attr( 'autofocus', 'autofocus' ); |
105 |
} |
106 |
if ( config.autocomplete === false ) { |
107 |
this.$input.attr( 'autocomplete', 'off' ); |
108 |
// Turning off autocompletion also disables "form caching" when the user navigates to a |
109 |
// different page and then clicks "Back". Re-enable it when leaving. Borrowed from jQuery UI. |
110 |
$( window ).on( { |
111 |
beforeunload: function () { |
112 |
this.$input.removeAttr( 'autocomplete' ); |
113 |
}.bind( this ), |
114 |
pageshow: function () { |
115 |
// Browsers don't seem to actually fire this event on "Back", they instead just reload the |
116 |
// whole page... it shouldn't hurt, though. |
117 |
this.$input.attr( 'autocomplete', 'off' ); |
118 |
}.bind( this ) |
119 |
} ); |
120 |
} |
121 |
if ( config.spellcheck !== undefined ) { |
122 |
this.$input.attr( 'spellcheck', config.spellcheck ? 'true' : 'false' ); |
123 |
} |
124 |
if ( this.label ) { |
125 |
this.isWaitingToBeAttached = true; |
126 |
this.installParentChangeDetector(); |
127 |
} |
128 |
}; |
129 |
|
130 |
/* Setup */ |
131 |
|
132 |
OO.inheritClass( OO.ui.TextInputWidget, OO.ui.InputWidget ); |
133 |
OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.IconElement ); |
134 |
OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.IndicatorElement ); |
135 |
OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.PendingElement ); |
136 |
OO.mixinClass( OO.ui.TextInputWidget, OO.ui.mixin.LabelElement ); |
137 |
|
138 |
/* Static Properties */ |
139 |
|
140 |
OO.ui.TextInputWidget.static.validationPatterns = { |
141 |
'non-empty': /.+/, |
142 |
integer: /^\d+$/ |
143 |
}; |
144 |
|
145 |
/* Events */ |
146 |
|
147 |
/** |
148 |
* An `enter` event is emitted when the user presses 'enter' inside the text box. |
149 |
* |
150 |
* @event enter |
151 |
*/ |
152 |
|
153 |
/* Methods */ |
154 |
|
155 |
/** |
156 |
* Handle icon mouse down events. |
157 |
* |
158 |
* @private |
159 |
* @param {jQuery.Event} e Mouse down event |
160 |
*/ |
161 |
OO.ui.TextInputWidget.prototype.onIconMouseDown = function ( e ) { |
162 |
if ( e.which === OO.ui.MouseButtons.LEFT ) { |
163 |
this.focus(); |
164 |
return false; |
165 |
} |
166 |
}; |
167 |
|
168 |
/** |
169 |
* Handle indicator mouse down events. |
170 |
* |
171 |
* @private |
172 |
* @param {jQuery.Event} e Mouse down event |
173 |
*/ |
174 |
OO.ui.TextInputWidget.prototype.onIndicatorMouseDown = function ( e ) { |
175 |
if ( e.which === OO.ui.MouseButtons.LEFT ) { |
176 |
this.focus(); |
177 |
return false; |
178 |
} |
179 |
}; |
180 |
|
181 |
/** |
182 |
* Handle key press events. |
183 |
* |
184 |
* @private |
185 |
* @param {jQuery.Event} e Key press event |
186 |
* @fires enter If enter key is pressed |
187 |
*/ |
188 |
OO.ui.TextInputWidget.prototype.onKeyPress = function ( e ) { |
189 |
if ( e.which === OO.ui.Keys.ENTER ) { |
190 |
this.emit( 'enter', e ); |
191 |
} |
192 |
}; |
193 |
|
194 |
/** |
195 |
* Handle blur events. |
196 |
* |
197 |
* @private |
198 |
* @param {jQuery.Event} e Blur event |
199 |
*/ |
200 |
OO.ui.TextInputWidget.prototype.onBlur = function () { |
201 |
this.setValidityFlag(); |
202 |
}; |
203 |
|
204 |
/** |
205 |
* Handle focus events. |
206 |
* |
207 |
* @private |
208 |
* @param {jQuery.Event} e Focus event |
209 |
*/ |
210 |
OO.ui.TextInputWidget.prototype.onFocus = function () { |
211 |
if ( this.isWaitingToBeAttached ) { |
212 |
// If we've received focus, then we must be attached to the document, and if |
213 |
// isWaitingToBeAttached is still true, that means the handler never fired. Fire it now. |
214 |
this.onElementAttach(); |
215 |
} |
216 |
this.setValidityFlag( true ); |
217 |
}; |
218 |
|
219 |
/** |
220 |
* Handle element attach events. |
221 |
* |
222 |
* @private |
223 |
* @param {jQuery.Event} e Element attach event |
224 |
*/ |
225 |
OO.ui.TextInputWidget.prototype.onElementAttach = function () { |
226 |
this.isWaitingToBeAttached = false; |
227 |
// Any previously calculated size is now probably invalid if we reattached elsewhere |
228 |
this.valCache = null; |
229 |
this.positionLabel(); |
230 |
}; |
231 |
|
232 |
/** |
233 |
* Handle debounced change events. |
234 |
* |
235 |
* @param {string} value |
236 |
* @private |
237 |
*/ |
238 |
OO.ui.TextInputWidget.prototype.onDebouncedChange = function () { |
239 |
this.setValidityFlag(); |
240 |
}; |
241 |
|
242 |
/** |
243 |
* Check if the input is {@link #readOnly read-only}. |
244 |
* |
245 |
* @return {boolean} |
246 |
*/ |
247 |
OO.ui.TextInputWidget.prototype.isReadOnly = function () { |
248 |
return this.readOnly; |
249 |
}; |
250 |
|
251 |
/** |
252 |
* Set the {@link #readOnly read-only} state of the input. |
253 |
* |
254 |
* @param {boolean} state Make input read-only |
255 |
* @chainable |
256 |
*/ |
257 |
OO.ui.TextInputWidget.prototype.setReadOnly = function ( state ) { |
258 |
this.readOnly = !!state; |
259 |
this.$input.prop( 'readOnly', this.readOnly ); |
260 |
return this; |
261 |
}; |
262 |
|
263 |
/** |
264 |
* Check if the input is {@link #required required}. |
265 |
* |
266 |
* @return {boolean} |
267 |
*/ |
268 |
OO.ui.TextInputWidget.prototype.isRequired = function () { |
269 |
return this.required; |
270 |
}; |
271 |
|
272 |
/** |
273 |
* Set the {@link #required required} state of the input. |
274 |
* |
275 |
* @param {boolean} state Make input required |
276 |
* @chainable |
277 |
*/ |
278 |
OO.ui.TextInputWidget.prototype.setRequired = function ( state ) { |
279 |
this.required = !!state; |
280 |
if ( this.required ) { |
281 |
this.$input |
282 |
.prop( 'required', true ) |
283 |
.attr( 'aria-required', 'true' ); |
284 |
if ( this.getIndicator() === null ) { |
285 |
this.setIndicator( 'required' ); |
286 |
} |
287 |
} else { |
288 |
this.$input |
289 |
.prop( 'required', false ) |
290 |
.removeAttr( 'aria-required' ); |
291 |
if ( this.getIndicator() === 'required' ) { |
292 |
this.setIndicator( null ); |
293 |
} |
294 |
} |
295 |
return this; |
296 |
}; |
297 |
|
298 |
/** |
299 |
* Support function for making #onElementAttach work across browsers. |
300 |
* |
301 |
* This whole function could be replaced with one line of code using the DOMNodeInsertedIntoDocument |
302 |
* event, but it's not supported by Firefox and allegedly deprecated, so we only use it as fallback. |
303 |
* |
304 |
* Due to MutationObserver performance woes, #onElementAttach is only somewhat reliably called the |
305 |
* first time that the element gets attached to the documented. |
306 |
*/ |
307 |
OO.ui.TextInputWidget.prototype.installParentChangeDetector = function () { |
308 |
var mutationObserver, onRemove, topmostNode, fakeParentNode, |
309 |
MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver, |
310 |
widget = this; |
311 |
|
312 |
if ( MutationObserver ) { |
313 |
// The new way. If only it wasn't so ugly. |
314 |
|
315 |
if ( this.isElementAttached() ) { |
316 |
// Widget is attached already, do nothing. This breaks the functionality of this function when |
317 |
// the widget is detached and reattached. Alas, doing this correctly with MutationObserver |
318 |
// would require observation of the whole document, which would hurt performance of other, |
319 |
// more important code. |
320 |
return; |
321 |
} |
322 |
|
323 |
// Find topmost node in the tree |
324 |
topmostNode = this.$element[ 0 ]; |
325 |
while ( topmostNode.parentNode ) { |
326 |
topmostNode = topmostNode.parentNode; |
327 |
} |
328 |
|
329 |
// We have no way to detect the $element being attached somewhere without observing the entire |
330 |
// DOM with subtree modifications, which would hurt performance. So we cheat: we hook to the |
331 |
// parent node of $element, and instead detect when $element is removed from it (and thus |
332 |
// probably attached somewhere else). If there is no parent, we create a "fake" one. If it |
333 |
// doesn't get attached, we end up back here and create the parent. |
334 |
|
335 |
mutationObserver = new MutationObserver( function ( mutations ) { |
336 |
var i, j, removedNodes; |
337 |
for ( i = 0; i < mutations.length; i++ ) { |
338 |
removedNodes = mutations[ i ].removedNodes; |
339 |
for ( j = 0; j < removedNodes.length; j++ ) { |
340 |
if ( removedNodes[ j ] === topmostNode ) { |
341 |
setTimeout( onRemove, 0 ); |
342 |
return; |
343 |
} |
344 |
} |
345 |
} |
346 |
} ); |
347 |
|
348 |
onRemove = function () { |
349 |
// If the node was attached somewhere else, report it |
350 |
if ( widget.isElementAttached() ) { |
351 |
widget.onElementAttach(); |
352 |
} |
353 |
mutationObserver.disconnect(); |
354 |
widget.installParentChangeDetector(); |
355 |
}; |
356 |
|
357 |
// Create a fake parent and observe it |
358 |
fakeParentNode = $( '<div>' ).append( topmostNode )[ 0 ]; |
359 |
mutationObserver.observe( fakeParentNode, { childList: true } ); |
360 |
} else { |
361 |
// Using the DOMNodeInsertedIntoDocument event is much nicer and less magical, and works for |
362 |
// detachment and reattachment, but it's not supported by Firefox and allegedly deprecated. |
363 |
this.$element.on( 'DOMNodeInsertedIntoDocument', this.onElementAttach.bind( this ) ); |
364 |
} |
365 |
}; |
366 |
|
367 |
/** |
368 |
* @inheritdoc |
369 |
* @protected |
370 |
*/ |
371 |
OO.ui.TextInputWidget.prototype.getInputElement = function ( config ) { |
372 |
if ( this.getSaneType( config ) === 'number' ) { |
Error |
Row 373, Column 10: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Error |
Row 373, Column 10: "Prefer setAttribute to $.attr"
jquery/no-attr
|
373 |
return $( '<input>' ) |
374 |
.attr( 'step', 'any' ) |
375 |
.attr( 'type', 'number' ); |
376 |
} else { |
Error |
Row 377, Column 10: "Prefer setAttribute to $.attr"
jquery/no-attr
|
377 |
return $( '<input>' ).attr( 'type', this.getSaneType( config ) ); |
378 |
} |
379 |
}; |
380 |
|
381 |
/** |
382 |
* Get sanitized value for 'type' for given config. |
383 |
* |
384 |
* @param {Object} config Configuration options |
385 |
* @return {string|null} |
386 |
* @protected |
387 |
*/ |
388 |
OO.ui.TextInputWidget.prototype.getSaneType = function ( config ) { |
389 |
var allowedTypes = [ |
390 |
'text', |
391 |
'password', |
392 |
'email', |
393 |
'url', |
394 |
'number' |
395 |
]; |
396 |
return allowedTypes.indexOf( config.type ) !== -1 ? config.type : 'text'; |
397 |
}; |
398 |
|
399 |
/** |
400 |
* Focus the input and select a specified range within the text. |
401 |
* |
402 |
* @param {number} from Select from offset |
403 |
* @param {number} [to] Select to offset, defaults to from |
404 |
* @chainable |
405 |
*/ |
406 |
OO.ui.TextInputWidget.prototype.selectRange = function ( from, to ) { |
407 |
var isBackwards, start, end, |
408 |
input = this.$input[ 0 ]; |
409 |
|
410 |
to = to || from; |
411 |
|
412 |
isBackwards = to < from; |
413 |
start = isBackwards ? to : from; |
414 |
end = isBackwards ? from : to; |
415 |
|
416 |
this.focus(); |
417 |
|
418 |
try { |
419 |
input.setSelectionRange( start, end, isBackwards ? 'backward' : 'forward' ); |
420 |
} catch ( e ) { |
421 |
// IE throws an exception if you call setSelectionRange on a unattached DOM node. |
422 |
// Rather than expensively check if the input is attached every time, just check |
423 |
// if it was the cause of an error being thrown. If not, rethrow the error. |
424 |
if ( this.getElementDocument().body.contains( input ) ) { |
425 |
throw e; |
426 |
} |
427 |
} |
428 |
return this; |
429 |
}; |
430 |
|
431 |
/** |
432 |
* Get an object describing the current selection range in a directional manner |
433 |
* |
434 |
* @return {Object} Object containing 'from' and 'to' offsets |
435 |
*/ |
436 |
OO.ui.TextInputWidget.prototype.getRange = function () { |
437 |
var input = this.$input[ 0 ], |
438 |
start = input.selectionStart, |
439 |
end = input.selectionEnd, |
440 |
isBackwards = input.selectionDirection === 'backward'; |
441 |
|
442 |
return { |
443 |
from: isBackwards ? end : start, |
444 |
to: isBackwards ? start : end |
445 |
}; |
446 |
}; |
447 |
|
448 |
/** |
449 |
* Get the length of the text input value. |
450 |
* |
451 |
* This could differ from the length of #getValue if the |
452 |
* value gets filtered |
453 |
* |
454 |
* @return {number} Input length |
455 |
*/ |
456 |
OO.ui.TextInputWidget.prototype.getInputLength = function () { |
457 |
return this.$input[ 0 ].value.length; |
458 |
}; |
459 |
|
460 |
/** |
461 |
* Focus the input and select the entire text. |
462 |
* |
463 |
* @chainable |
464 |
*/ |
465 |
OO.ui.TextInputWidget.prototype.select = function () { |
466 |
return this.selectRange( 0, this.getInputLength() ); |
467 |
}; |
468 |
|
469 |
/** |
470 |
* Focus the input and move the cursor to the start. |
471 |
* |
472 |
* @chainable |
473 |
*/ |
474 |
OO.ui.TextInputWidget.prototype.moveCursorToStart = function () { |
475 |
return this.selectRange( 0 ); |
476 |
}; |
477 |
|
478 |
/** |
479 |
* Focus the input and move the cursor to the end. |
480 |
* |
481 |
* @chainable |
482 |
*/ |
483 |
OO.ui.TextInputWidget.prototype.moveCursorToEnd = function () { |
484 |
return this.selectRange( this.getInputLength() ); |
485 |
}; |
486 |
|
487 |
/** |
488 |
* Insert new content into the input. |
489 |
* |
490 |
* @param {string} content Content to be inserted |
491 |
* @chainable |
492 |
*/ |
493 |
OO.ui.TextInputWidget.prototype.insertContent = function ( content ) { |
494 |
var start, end, |
495 |
range = this.getRange(), |
496 |
value = this.getValue(); |
497 |
|
498 |
start = Math.min( range.from, range.to ); |
499 |
end = Math.max( range.from, range.to ); |
500 |
|
501 |
this.setValue( value.slice( 0, start ) + content + value.slice( end ) ); |
502 |
this.selectRange( start + content.length ); |
503 |
return this; |
504 |
}; |
505 |
|
506 |
/** |
507 |
* Insert new content either side of a selection. |
508 |
* |
509 |
* @param {string} pre Content to be inserted before the selection |
510 |
* @param {string} post Content to be inserted after the selection |
511 |
* @chainable |
512 |
*/ |
513 |
OO.ui.TextInputWidget.prototype.encapsulateContent = function ( pre, post ) { |
514 |
var start, end, |
515 |
range = this.getRange(), |
516 |
offset = pre.length; |
517 |
|
518 |
start = Math.min( range.from, range.to ); |
519 |
end = Math.max( range.from, range.to ); |
520 |
|
521 |
this.selectRange( start ).insertContent( pre ); |
522 |
this.selectRange( offset + end ).insertContent( post ); |
523 |
|
524 |
this.selectRange( offset + start, offset + end ); |
525 |
return this; |
526 |
}; |
527 |
|
528 |
/** |
529 |
* Set the validation pattern. |
530 |
* |
531 |
* The validation pattern is either a regular expression, a function, or the symbolic name of a |
532 |
* pattern defined by the class: 'non-empty' (the value cannot be an empty string) or 'integer' (the |
533 |
* value must contain only numbers). |
534 |
* |
535 |
* @param {RegExp|Function|string|null} validate Regular expression, function, or the symbolic name |
536 |
* of a pattern (either ‘integer’ or ‘non-empty’) defined by the class. |
537 |
*/ |
538 |
OO.ui.TextInputWidget.prototype.setValidation = function ( validate ) { |
539 |
if ( validate instanceof RegExp || validate instanceof Function ) { |
540 |
this.validate = validate; |
541 |
} else { |
542 |
this.validate = this.constructor.static.validationPatterns[ validate ] || /.*/; |
543 |
} |
544 |
}; |
545 |
|
546 |
/** |
547 |
* Sets the 'invalid' flag appropriately. |
548 |
* |
549 |
* @param {boolean} [isValid] Optionally override validation result |
550 |
*/ |
551 |
OO.ui.TextInputWidget.prototype.setValidityFlag = function ( isValid ) { |
552 |
var widget = this, |
553 |
setFlag = function ( valid ) { |
554 |
if ( !valid ) { |
555 |
widget.$input.attr( 'aria-invalid', 'true' ); |
556 |
} else { |
557 |
widget.$input.removeAttr( 'aria-invalid' ); |
558 |
} |
559 |
widget.setFlags( { invalid: !valid } ); |
560 |
}; |
561 |
|
562 |
if ( isValid !== undefined ) { |
563 |
setFlag( isValid ); |
564 |
} else { |
565 |
this.getValidity().then( function () { |
566 |
setFlag( true ); |
567 |
}, function () { |
568 |
setFlag( false ); |
569 |
} ); |
570 |
} |
571 |
}; |
572 |
|
573 |
/** |
574 |
* Get the validity of current value. |
575 |
* |
576 |
* This method returns a promise that resolves if the value is valid and rejects if |
577 |
* it isn't. Uses the {@link #validate validation pattern} to check for validity. |
578 |
* |
579 |
* @return {jQuery.Promise} A promise that resolves if the value is valid, rejects if not. |
580 |
*/ |
581 |
OO.ui.TextInputWidget.prototype.getValidity = function () { |
582 |
var result; |
583 |
|
584 |
function rejectOrResolve( valid ) { |
585 |
if ( valid ) { |
Error |
Row 586, Column 11: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
586 |
return $.Deferred().resolve().promise(); |
587 |
} else { |
Error |
Row 588, Column 11: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
588 |
return $.Deferred().reject().promise(); |
589 |
} |
590 |
} |
591 |
|
592 |
// Check browser validity and reject if it is invalid |
593 |
if ( |
594 |
this.$input[ 0 ].checkValidity !== undefined && |
595 |
this.$input[ 0 ].checkValidity() === false |
596 |
) { |
597 |
return rejectOrResolve( false ); |
598 |
} |
599 |
|
600 |
// Run our checks if the browser thinks the field is valid |
601 |
if ( this.validate instanceof Function ) { |
602 |
result = this.validate( this.getValue() ); |
603 |
if ( result && $.isFunction( result.promise ) ) { |
604 |
return result.promise().then( function ( valid ) { |
605 |
return rejectOrResolve( valid ); |
606 |
} ); |
607 |
} else { |
608 |
return rejectOrResolve( result ); |
609 |
} |
610 |
} else { |
611 |
return rejectOrResolve( this.getValue().match( this.validate ) ); |
612 |
} |
613 |
}; |
614 |
|
615 |
/** |
616 |
* Set the position of the inline label relative to that of the value: `‘before’` or `‘after’`. |
617 |
* |
618 |
* @param {string} labelPosition Label position, 'before' or 'after' |
619 |
* @chainable |
620 |
*/ |
621 |
OO.ui.TextInputWidget.prototype.setLabelPosition = function ( labelPosition ) { |
622 |
this.labelPosition = labelPosition; |
623 |
if ( this.label ) { |
624 |
// If there is no label and we only change the position, #updatePosition is a no-op, |
625 |
// but it takes really a lot of work to do nothing. |
626 |
this.updatePosition(); |
627 |
} |
628 |
return this; |
629 |
}; |
630 |
|
631 |
/** |
632 |
* Update the position of the inline label. |
633 |
* |
634 |
* This method is called by #setLabelPosition, and can also be called on its own if |
635 |
* something causes the label to be mispositioned. |
636 |
* |
637 |
* @chainable |
638 |
*/ |
639 |
OO.ui.TextInputWidget.prototype.updatePosition = function () { |
640 |
var after = this.labelPosition === 'after'; |
641 |
|
642 |
this.$element |
643 |
.toggleClass( 'oo-ui-textInputWidget-labelPosition-after', !!this.label && after ) |
644 |
.toggleClass( 'oo-ui-textInputWidget-labelPosition-before', !!this.label && !after ); |
645 |
|
646 |
this.valCache = null; |
647 |
this.scrollWidth = null; |
648 |
this.positionLabel(); |
649 |
|
650 |
return this; |
651 |
}; |
652 |
|
653 |
/** |
654 |
* Position the label by setting the correct padding on the input. |
655 |
* |
656 |
* @private |
657 |
* @chainable |
658 |
*/ |
659 |
OO.ui.TextInputWidget.prototype.positionLabel = function () { |
660 |
var after, rtl, property, newCss; |
661 |
|
662 |
if ( this.isWaitingToBeAttached ) { |
663 |
// #onElementAttach will be called soon, which calls this method |
664 |
return this; |
665 |
} |
666 |
|
667 |
newCss = { |
668 |
'padding-right': '', |
669 |
'padding-left': '' |
670 |
}; |
671 |
|
672 |
if ( this.label ) { |
673 |
this.$element.append( this.$label ); |
674 |
} else { |
675 |
this.$label.detach(); |
676 |
// Clear old values if present |
677 |
this.$input.css( newCss ); |
678 |
return; |
679 |
} |
680 |
|
681 |
after = this.labelPosition === 'after'; |
682 |
rtl = this.$element.css( 'direction' ) === 'rtl'; |
683 |
property = after === rtl ? 'padding-left' : 'padding-right'; |
684 |
|
685 |
newCss[ property ] = this.$label.outerWidth( true ) + ( after ? this.scrollWidth : 0 ); |
686 |
// We have to clear the padding on the other side, in case the element direction changed |
687 |
this.$input.css( newCss ); |
688 |
|
689 |
return this; |
690 |
}; |
691 |
|
|
|
/src/widgets/ToggleButtonWidget.js
|
0 problems
|
|
/src/widgets/ToggleSwitchWidget.js
|
0 problems
|
|
/src/widgets/ToggleWidget.js
|
0 problems
|
|
/src/Window.js
|
2 problems (2 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 83, Column 26: "Prefer direct property access to $.prop"
jquery/no-prop
|
Error |
Row 84, Column 25: "Prefer direct property access to $.prop"
jquery/no-prop
|
Line |
Source |
1 |
/** |
2 |
* A window is a container for elements that are in a child frame. They are used with |
3 |
* a window manager (OO.ui.WindowManager), which is used to open and close the window and control |
4 |
* its presentation. The size of a window is specified using a symbolic name (e.g., ‘small’, ‘medium’, |
5 |
* ‘large’), which is interpreted by the window manager. If the requested size is not recognized, |
6 |
* the window manager will choose a sensible fallback. |
7 |
* |
8 |
* The lifecycle of a window has three primary stages (opening, opened, and closing) in which |
9 |
* different processes are executed: |
10 |
* |
11 |
* **opening**: The opening stage begins when the window manager's {@link OO.ui.WindowManager#openWindow |
12 |
* openWindow} or the window's {@link #open open} methods are used, and the window manager begins to open |
13 |
* the window. |
14 |
* |
15 |
* - {@link #getSetupProcess} method is called and its result executed |
16 |
* - {@link #getReadyProcess} method is called and its result executed |
17 |
* |
18 |
* **opened**: The window is now open |
19 |
* |
20 |
* **closing**: The closing stage begins when the window manager's |
21 |
* {@link OO.ui.WindowManager#closeWindow closeWindow} |
22 |
* or the window's {@link #close} methods are used, and the window manager begins to close the window. |
23 |
* |
24 |
* - {@link #getHoldProcess} method is called and its result executed |
25 |
* - {@link #getTeardownProcess} method is called and its result executed. The window is now closed |
26 |
* |
27 |
* Each of the window's processes (setup, ready, hold, and teardown) can be extended in subclasses |
28 |
* by overriding the window's #getSetupProcess, #getReadyProcess, #getHoldProcess and #getTeardownProcess |
29 |
* methods. Note that each {@link OO.ui.Process process} is executed in series, so asynchronous |
30 |
* processing can complete. Always assume window processes are executed asynchronously. |
31 |
* |
32 |
* For more information, please see the [OOUI documentation on MediaWiki] [1]. |
33 |
* |
34 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Windows |
35 |
* |
36 |
* @abstract |
37 |
* @class |
38 |
* @extends OO.ui.Element |
39 |
* @mixins OO.EventEmitter |
40 |
* |
41 |
* @constructor |
42 |
* @param {Object} [config] Configuration options |
43 |
* @cfg {string} [size] Symbolic name of the dialog size: `small`, `medium`, `large`, `larger` or |
44 |
* `full`. If omitted, the value of the {@link #static-size static size} property will be used. |
45 |
*/ |
46 |
OO.ui.Window = function OoUiWindow( config ) { |
47 |
// Configuration initialization |
48 |
config = config || {}; |
49 |
|
50 |
// Parent constructor |
51 |
OO.ui.Window.parent.call( this, config ); |
52 |
|
53 |
// Mixin constructors |
54 |
OO.EventEmitter.call( this ); |
55 |
|
56 |
// Properties |
57 |
this.manager = null; |
58 |
this.size = config.size || this.constructor.static.size; |
59 |
this.$frame = $( '<div>' ); |
60 |
/** |
61 |
* Overlay element to use for the `$overlay` configuration option of widgets that support it. |
62 |
* Things put inside of it are overlaid on top of the window and are not bound to its dimensions. |
63 |
* See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>. |
64 |
* |
65 |
* MyDialog.prototype.initialize = function () { |
66 |
* ... |
67 |
* var popupButton = new OO.ui.PopupButtonWidget( { |
68 |
* $overlay: this.$overlay, |
69 |
* label: 'Popup button', |
70 |
* popup: { |
71 |
* $content: $( '<p>Popup contents.</p><p>Popup contents.</p><p>Popup contents.</p>' ), |
72 |
* padded: true |
73 |
* } |
74 |
* } ); |
75 |
* ... |
76 |
* }; |
77 |
* |
78 |
* @property {jQuery} |
79 |
*/ |
80 |
this.$overlay = $( '<div>' ); |
81 |
this.$content = $( '<div>' ); |
82 |
|
Error |
Row 83, Column 26: "Prefer direct property access to $.prop"
jquery/no-prop
|
83 |
this.$focusTrapBefore = $( '<div>' ).prop( 'tabIndex', 0 ); |
Error |
Row 84, Column 25: "Prefer direct property access to $.prop"
jquery/no-prop
|
84 |
this.$focusTrapAfter = $( '<div>' ).prop( 'tabIndex', 0 ); |
85 |
this.$focusTraps = this.$focusTrapBefore.add( this.$focusTrapAfter ); |
86 |
|
87 |
// Initialization |
88 |
this.$overlay.addClass( 'oo-ui-window-overlay' ); |
89 |
this.$content |
90 |
.addClass( 'oo-ui-window-content' ) |
91 |
.attr( 'tabindex', 0 ); |
92 |
this.$frame |
93 |
.addClass( 'oo-ui-window-frame' ) |
94 |
.append( this.$focusTrapBefore, this.$content, this.$focusTrapAfter ); |
95 |
|
96 |
this.$element |
97 |
.addClass( 'oo-ui-window' ) |
98 |
.append( this.$frame, this.$overlay ); |
99 |
|
100 |
// Initially hidden - using #toggle may cause errors if subclasses override toggle with methods |
101 |
// that reference properties not initialized at that time of parent class construction |
102 |
// TODO: Find a better way to handle post-constructor setup |
103 |
this.visible = false; |
104 |
this.$element.addClass( 'oo-ui-element-hidden' ); |
105 |
}; |
106 |
|
107 |
/* Setup */ |
108 |
|
109 |
OO.inheritClass( OO.ui.Window, OO.ui.Element ); |
110 |
OO.mixinClass( OO.ui.Window, OO.EventEmitter ); |
111 |
|
112 |
/* Static Properties */ |
113 |
|
114 |
/** |
115 |
* Symbolic name of the window size: `small`, `medium`, `large`, `larger` or `full`. |
116 |
* |
117 |
* The static size is used if no #size is configured during construction. |
118 |
* |
119 |
* @static |
120 |
* @inheritable |
121 |
* @property {string} |
122 |
*/ |
123 |
OO.ui.Window.static.size = 'medium'; |
124 |
|
125 |
/* Methods */ |
126 |
|
127 |
/** |
128 |
* Handle mouse down events. |
129 |
* |
130 |
* @private |
131 |
* @param {jQuery.Event} e Mouse down event |
132 |
*/ |
133 |
OO.ui.Window.prototype.onMouseDown = function ( e ) { |
134 |
// Prevent clicking on the click-block from stealing focus |
135 |
if ( e.target === this.$element[ 0 ] ) { |
136 |
return false; |
137 |
} |
138 |
}; |
139 |
|
140 |
/** |
141 |
* Check if the window has been initialized. |
142 |
* |
143 |
* Initialization occurs when a window is added to a manager. |
144 |
* |
145 |
* @return {boolean} Window has been initialized |
146 |
*/ |
147 |
OO.ui.Window.prototype.isInitialized = function () { |
148 |
return !!this.manager; |
149 |
}; |
150 |
|
151 |
/** |
152 |
* Check if the window is visible. |
153 |
* |
154 |
* @return {boolean} Window is visible |
155 |
*/ |
156 |
OO.ui.Window.prototype.isVisible = function () { |
157 |
return this.visible; |
158 |
}; |
159 |
|
160 |
/** |
161 |
* Check if the window is opening. |
162 |
* |
163 |
* This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpening isOpening} |
164 |
* method. |
165 |
* |
166 |
* @return {boolean} Window is opening |
167 |
*/ |
168 |
OO.ui.Window.prototype.isOpening = function () { |
169 |
return this.manager.isOpening( this ); |
170 |
}; |
171 |
|
172 |
/** |
173 |
* Check if the window is closing. |
174 |
* |
175 |
* This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isClosing isClosing} method. |
176 |
* |
177 |
* @return {boolean} Window is closing |
178 |
*/ |
179 |
OO.ui.Window.prototype.isClosing = function () { |
180 |
return this.manager.isClosing( this ); |
181 |
}; |
182 |
|
183 |
/** |
184 |
* Check if the window is opened. |
185 |
* |
186 |
* This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpened isOpened} method. |
187 |
* |
188 |
* @return {boolean} Window is opened |
189 |
*/ |
190 |
OO.ui.Window.prototype.isOpened = function () { |
191 |
return this.manager.isOpened( this ); |
192 |
}; |
193 |
|
194 |
/** |
195 |
* Get the window manager. |
196 |
* |
197 |
* All windows must be attached to a window manager, which is used to open |
198 |
* and close the window and control its presentation. |
199 |
* |
200 |
* @return {OO.ui.WindowManager} Manager of window |
201 |
*/ |
202 |
OO.ui.Window.prototype.getManager = function () { |
203 |
return this.manager; |
204 |
}; |
205 |
|
206 |
/** |
207 |
* Get the symbolic name of the window size (e.g., `small` or `medium`). |
208 |
* |
209 |
* @return {string} Symbolic name of the size: `small`, `medium`, `large`, `larger`, `full` |
210 |
*/ |
211 |
OO.ui.Window.prototype.getSize = function () { |
212 |
var viewport = OO.ui.Element.static.getDimensions( this.getElementWindow() ), |
213 |
sizes = this.manager.constructor.static.sizes, |
214 |
size = this.size; |
215 |
|
216 |
if ( !sizes[ size ] ) { |
217 |
size = this.manager.constructor.static.defaultSize; |
218 |
} |
219 |
if ( size !== 'full' && viewport.rect.right - viewport.rect.left < sizes[ size ].width ) { |
220 |
size = 'full'; |
221 |
} |
222 |
|
223 |
return size; |
224 |
}; |
225 |
|
226 |
/** |
227 |
* Get the size properties associated with the current window size |
228 |
* |
229 |
* @return {Object} Size properties |
230 |
*/ |
231 |
OO.ui.Window.prototype.getSizeProperties = function () { |
232 |
return this.manager.constructor.static.sizes[ this.getSize() ]; |
233 |
}; |
234 |
|
235 |
/** |
236 |
* Disable transitions on window's frame for the duration of the callback function, then enable them |
237 |
* back. |
238 |
* |
239 |
* @private |
240 |
* @param {Function} callback Function to call while transitions are disabled |
241 |
*/ |
242 |
OO.ui.Window.prototype.withoutSizeTransitions = function ( callback ) { |
243 |
// Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements. |
244 |
// Disable transitions first, otherwise we'll get values from when the window was animating. |
245 |
// We need to build the transition CSS properties using these specific properties since |
246 |
// Firefox doesn't return anything useful when asked just for 'transition'. |
247 |
var oldTransition = this.$frame.css( 'transition-property' ) + ' ' + |
248 |
this.$frame.css( 'transition-duration' ) + ' ' + |
249 |
this.$frame.css( 'transition-timing-function' ) + ' ' + |
250 |
this.$frame.css( 'transition-delay' ); |
251 |
|
252 |
this.$frame.css( 'transition', 'none' ); |
253 |
callback(); |
254 |
|
255 |
// Force reflow to make sure the style changes done inside callback |
256 |
// really are not transitioned |
257 |
this.$frame.height(); |
258 |
this.$frame.css( 'transition', oldTransition ); |
259 |
}; |
260 |
|
261 |
/** |
262 |
* Get the height of the full window contents (i.e., the window head, body and foot together). |
263 |
* |
264 |
* What constitutes the head, body, and foot varies depending on the window type. |
265 |
* A {@link OO.ui.MessageDialog message dialog} displays a title and message in its body, |
266 |
* and any actions in the foot. A {@link OO.ui.ProcessDialog process dialog} displays a title |
267 |
* and special actions in the head, and dialog content in the body. |
268 |
* |
269 |
* To get just the height of the dialog body, use the #getBodyHeight method. |
270 |
* |
271 |
* @return {number} The height of the window contents (the dialog head, body and foot) in pixels |
272 |
*/ |
273 |
OO.ui.Window.prototype.getContentHeight = function () { |
274 |
var bodyHeight, |
275 |
win = this, |
276 |
bodyStyleObj = this.$body[ 0 ].style, |
277 |
frameStyleObj = this.$frame[ 0 ].style; |
278 |
|
279 |
// Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements. |
280 |
// Disable transitions first, otherwise we'll get values from when the window was animating. |
281 |
this.withoutSizeTransitions( function () { |
282 |
var oldHeight = frameStyleObj.height, |
283 |
oldPosition = bodyStyleObj.position; |
284 |
frameStyleObj.height = '1px'; |
285 |
// Force body to resize to new width |
286 |
bodyStyleObj.position = 'relative'; |
287 |
bodyHeight = win.getBodyHeight(); |
288 |
frameStyleObj.height = oldHeight; |
289 |
bodyStyleObj.position = oldPosition; |
290 |
} ); |
291 |
|
292 |
return ( |
293 |
// Add buffer for border |
294 |
( this.$frame.outerHeight() - this.$frame.innerHeight() ) + |
295 |
// Use combined heights of children |
296 |
( this.$head.outerHeight( true ) + bodyHeight + this.$foot.outerHeight( true ) ) |
297 |
); |
298 |
}; |
299 |
|
300 |
/** |
301 |
* Get the height of the window body. |
302 |
* |
303 |
* To get the height of the full window contents (the window body, head, and foot together), |
304 |
* use #getContentHeight. |
305 |
* |
306 |
* When this function is called, the window will temporarily have been resized |
307 |
* to height=1px, so .scrollHeight measurements can be taken accurately. |
308 |
* |
309 |
* @return {number} Height of the window body in pixels |
310 |
*/ |
311 |
OO.ui.Window.prototype.getBodyHeight = function () { |
312 |
return this.$body[ 0 ].scrollHeight; |
313 |
}; |
314 |
|
315 |
/** |
316 |
* Get the directionality of the frame (right-to-left or left-to-right). |
317 |
* |
318 |
* @return {string} Directionality: `'ltr'` or `'rtl'` |
319 |
*/ |
320 |
OO.ui.Window.prototype.getDir = function () { |
321 |
return OO.ui.Element.static.getDir( this.$content ) || 'ltr'; |
322 |
}; |
323 |
|
324 |
/** |
325 |
* Get the 'setup' process. |
326 |
* |
327 |
* The setup process is used to set up a window for use in a particular context, based on the `data` |
328 |
* argument. This method is called during the opening phase of the window’s lifecycle (before the |
329 |
* opening animation). You can add elements to the window in this process or set their default |
330 |
* values. |
331 |
* |
332 |
* Override this method to add additional steps to the ‘setup’ process the parent method provides |
333 |
* using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods |
334 |
* of OO.ui.Process. |
335 |
* |
336 |
* To add window content that persists between openings, you may wish to use the #initialize method |
337 |
* instead. |
338 |
* |
339 |
* @param {Object} [data] Window opening data |
340 |
* @return {OO.ui.Process} Setup process |
341 |
*/ |
342 |
OO.ui.Window.prototype.getSetupProcess = function () { |
343 |
return new OO.ui.Process(); |
344 |
}; |
345 |
|
346 |
/** |
347 |
* Get the ‘ready’ process. |
348 |
* |
349 |
* The ready process is used to ready a window for use in a particular context, based on the `data` |
350 |
* argument. This method is called during the opening phase of the window’s lifecycle, after the |
351 |
* window has been {@link #getSetupProcess setup} (after the opening animation). You can focus |
352 |
* elements in the window in this process, or open their dropdowns. |
353 |
* |
354 |
* Override this method to add additional steps to the ‘ready’ process the parent method |
355 |
* provides using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} |
356 |
* methods of OO.ui.Process. |
357 |
* |
358 |
* @param {Object} [data] Window opening data |
359 |
* @return {OO.ui.Process} Ready process |
360 |
*/ |
361 |
OO.ui.Window.prototype.getReadyProcess = function () { |
362 |
return new OO.ui.Process(); |
363 |
}; |
364 |
|
365 |
/** |
366 |
* Get the 'hold' process. |
367 |
* |
368 |
* The hold process is used to keep a window from being used in a particular context, based on the |
369 |
* `data` argument. This method is called during the closing phase of the window’s lifecycle (before |
370 |
* the closing animation). You can close dropdowns of elements in the window in this process, if |
371 |
* they do not get closed automatically. |
372 |
* |
373 |
* Override this method to add additional steps to the 'hold' process the parent method provides |
374 |
* using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods |
375 |
* of OO.ui.Process. |
376 |
* |
377 |
* @param {Object} [data] Window closing data |
378 |
* @return {OO.ui.Process} Hold process |
379 |
*/ |
380 |
OO.ui.Window.prototype.getHoldProcess = function () { |
381 |
return new OO.ui.Process(); |
382 |
}; |
383 |
|
384 |
/** |
385 |
* Get the ‘teardown’ process. |
386 |
* |
387 |
* The teardown process is used to teardown a window after use. During teardown, user interactions |
388 |
* within the window are conveyed and the window is closed, based on the `data` argument. This |
389 |
* method is called during the closing phase of the window’s lifecycle (after the closing |
390 |
* animation). You can remove elements in the window in this process or clear their values. |
391 |
* |
392 |
* Override this method to add additional steps to the ‘teardown’ process the parent method provides |
393 |
* using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods |
394 |
* of OO.ui.Process. |
395 |
* |
396 |
* @param {Object} [data] Window closing data |
397 |
* @return {OO.ui.Process} Teardown process |
398 |
*/ |
399 |
OO.ui.Window.prototype.getTeardownProcess = function () { |
400 |
return new OO.ui.Process(); |
401 |
}; |
402 |
|
403 |
/** |
404 |
* Set the window manager. |
405 |
* |
406 |
* This will cause the window to initialize. Calling it more than once will cause an error. |
407 |
* |
408 |
* @param {OO.ui.WindowManager} manager Manager for this window |
409 |
* @throws {Error} An error is thrown if the method is called more than once |
410 |
* @chainable |
411 |
*/ |
412 |
OO.ui.Window.prototype.setManager = function ( manager ) { |
413 |
if ( this.manager ) { |
414 |
throw new Error( 'Cannot set window manager, window already has a manager' ); |
415 |
} |
416 |
|
417 |
this.manager = manager; |
418 |
this.initialize(); |
419 |
|
420 |
return this; |
421 |
}; |
422 |
|
423 |
/** |
424 |
* Set the window size by symbolic name (e.g., 'small' or 'medium') |
425 |
* |
426 |
* @param {string} size Symbolic name of size: `small`, `medium`, `large`, `larger` or |
427 |
* `full` |
428 |
* @chainable |
429 |
*/ |
430 |
OO.ui.Window.prototype.setSize = function ( size ) { |
431 |
this.size = size; |
432 |
this.updateSize(); |
433 |
return this; |
434 |
}; |
435 |
|
436 |
/** |
437 |
* Update the window size. |
438 |
* |
439 |
* @throws {Error} An error is thrown if the window is not attached to a window manager |
440 |
* @chainable |
441 |
*/ |
442 |
OO.ui.Window.prototype.updateSize = function () { |
443 |
if ( !this.manager ) { |
444 |
throw new Error( 'Cannot update window size, must be attached to a manager' ); |
445 |
} |
446 |
|
447 |
this.manager.updateWindowSize( this ); |
448 |
|
449 |
return this; |
450 |
}; |
451 |
|
452 |
/** |
453 |
* Set window dimensions. This method is called by the {@link OO.ui.WindowManager window manager} |
454 |
* when the window is opening. In general, setDimensions should not be called directly. |
455 |
* |
456 |
* To set the size of the window, use the #setSize method. |
457 |
* |
458 |
* @param {Object} dim CSS dimension properties |
459 |
* @param {string|number} [dim.width] Width |
460 |
* @param {string|number} [dim.minWidth] Minimum width |
461 |
* @param {string|number} [dim.maxWidth] Maximum width |
462 |
* @param {string|number} [dim.height] Height, omit to set based on height of contents |
463 |
* @param {string|number} [dim.minHeight] Minimum height |
464 |
* @param {string|number} [dim.maxHeight] Maximum height |
465 |
* @chainable |
466 |
*/ |
467 |
OO.ui.Window.prototype.setDimensions = function ( dim ) { |
468 |
var height, |
469 |
win = this, |
470 |
styleObj = this.$frame[ 0 ].style; |
471 |
|
472 |
// Calculate the height we need to set using the correct width |
473 |
if ( dim.height === undefined ) { |
474 |
this.withoutSizeTransitions( function () { |
475 |
var oldWidth = styleObj.width; |
476 |
win.$frame.css( 'width', dim.width || '' ); |
477 |
height = win.getContentHeight(); |
478 |
styleObj.width = oldWidth; |
479 |
} ); |
480 |
} else { |
481 |
height = dim.height; |
482 |
} |
483 |
|
484 |
this.$frame.css( { |
485 |
width: dim.width || '', |
486 |
minWidth: dim.minWidth || '', |
487 |
maxWidth: dim.maxWidth || '', |
488 |
height: height || '', |
489 |
minHeight: dim.minHeight || '', |
490 |
maxHeight: dim.maxHeight || '' |
491 |
} ); |
492 |
|
493 |
return this; |
494 |
}; |
495 |
|
496 |
/** |
497 |
* Initialize window contents. |
498 |
* |
499 |
* Before the window is opened for the first time, #initialize is called so that content that |
500 |
* persists between openings can be added to the window. |
501 |
* |
502 |
* To set up a window with new content each time the window opens, use #getSetupProcess. |
503 |
* |
504 |
* @throws {Error} An error is thrown if the window is not attached to a window manager |
505 |
* @chainable |
506 |
*/ |
507 |
OO.ui.Window.prototype.initialize = function () { |
508 |
if ( !this.manager ) { |
509 |
throw new Error( 'Cannot initialize window, must be attached to a manager' ); |
510 |
} |
511 |
|
512 |
// Properties |
513 |
this.$head = $( '<div>' ); |
514 |
this.$body = $( '<div>' ); |
515 |
this.$foot = $( '<div>' ); |
516 |
this.$document = $( this.getElementDocument() ); |
517 |
|
518 |
// Events |
519 |
this.$element.on( 'mousedown', this.onMouseDown.bind( this ) ); |
520 |
|
521 |
// Initialization |
522 |
this.$head.addClass( 'oo-ui-window-head' ); |
523 |
this.$body.addClass( 'oo-ui-window-body' ); |
524 |
this.$foot.addClass( 'oo-ui-window-foot' ); |
525 |
this.$content.append( this.$head, this.$body, this.$foot ); |
526 |
|
527 |
return this; |
528 |
}; |
529 |
|
530 |
/** |
531 |
* Called when someone tries to focus the hidden element at the end of the dialog. |
532 |
* Sends focus back to the start of the dialog. |
533 |
* |
534 |
* @param {jQuery.Event} event Focus event |
535 |
*/ |
536 |
OO.ui.Window.prototype.onFocusTrapFocused = function ( event ) { |
537 |
var backwards = this.$focusTrapBefore.is( event.target ), |
538 |
element = OO.ui.findFocusable( this.$content, backwards ); |
539 |
if ( element ) { |
540 |
// There's a focusable element inside the content, at the front or |
541 |
// back depending on which focus trap we hit; select it. |
542 |
element.focus(); |
543 |
} else { |
544 |
// There's nothing focusable inside the content. As a fallback, |
545 |
// this.$content is focusable, and focusing it will keep our focus |
546 |
// properly trapped. It's not a *meaningful* focus, since it's just |
547 |
// the content-div for the Window, but it's better than letting focus |
548 |
// escape into the page. |
549 |
this.$content.focus(); |
550 |
} |
551 |
}; |
552 |
|
553 |
/** |
554 |
* Open the window. |
555 |
* |
556 |
* This method is a wrapper around a call to the window |
557 |
* manager’s {@link OO.ui.WindowManager#openWindow openWindow} method. |
558 |
* |
559 |
* To customize the window each time it opens, use #getSetupProcess or #getReadyProcess. |
560 |
* |
561 |
* @param {Object} [data] Window opening data |
562 |
* @return {OO.ui.WindowInstance} See OO.ui.WindowManager#openWindow |
563 |
* @throws {Error} An error is thrown if the window is not attached to a window manager |
564 |
*/ |
565 |
OO.ui.Window.prototype.open = function ( data ) { |
566 |
if ( !this.manager ) { |
567 |
throw new Error( 'Cannot open window, must be attached to a manager' ); |
568 |
} |
569 |
|
570 |
return this.manager.openWindow( this, data ); |
571 |
}; |
572 |
|
573 |
/** |
574 |
* Close the window. |
575 |
* |
576 |
* This method is a wrapper around a call to the window |
577 |
* manager’s {@link OO.ui.WindowManager#closeWindow closeWindow} method. |
578 |
* |
579 |
* The window's #getHoldProcess and #getTeardownProcess methods are called during the closing |
580 |
* phase of the window’s lifecycle and can be used to specify closing behavior each time |
581 |
* the window closes. |
582 |
* |
583 |
* @param {Object} [data] Window closing data |
584 |
* @return {OO.ui.WindowInstance} See OO.ui.WindowManager#closeWindow |
585 |
* @throws {Error} An error is thrown if the window is not attached to a window manager |
586 |
*/ |
587 |
OO.ui.Window.prototype.close = function ( data ) { |
588 |
if ( !this.manager ) { |
589 |
throw new Error( 'Cannot close window, must be attached to a manager' ); |
590 |
} |
591 |
|
592 |
return this.manager.closeWindow( this, data ); |
593 |
}; |
594 |
|
595 |
/** |
596 |
* Setup window. |
597 |
* |
598 |
* This is called by OO.ui.WindowManager during window opening (before the animation), and should |
599 |
* not be called directly by other systems. |
600 |
* |
601 |
* @param {Object} [data] Window opening data |
602 |
* @return {jQuery.Promise} Promise resolved when window is setup |
603 |
*/ |
604 |
OO.ui.Window.prototype.setup = function ( data ) { |
605 |
var win = this; |
606 |
|
607 |
this.toggle( true ); |
608 |
|
609 |
this.focusTrapHandler = OO.ui.bind( this.onFocusTrapFocused, this ); |
610 |
this.$focusTraps.on( 'focus', this.focusTrapHandler ); |
611 |
|
612 |
return this.getSetupProcess( data ).execute().then( function () { |
613 |
win.updateSize(); |
614 |
// Force redraw by asking the browser to measure the elements' widths |
615 |
win.$element.addClass( 'oo-ui-window-active oo-ui-window-setup' ).width(); |
616 |
win.$content.addClass( 'oo-ui-window-content-setup' ).width(); |
617 |
} ); |
618 |
}; |
619 |
|
620 |
/** |
621 |
* Ready window. |
622 |
* |
623 |
* This is called by OO.ui.WindowManager during window opening (after the animation), and should not |
624 |
* be called directly by other systems. |
625 |
* |
626 |
* @param {Object} [data] Window opening data |
627 |
* @return {jQuery.Promise} Promise resolved when window is ready |
628 |
*/ |
629 |
OO.ui.Window.prototype.ready = function ( data ) { |
630 |
var win = this; |
631 |
|
632 |
this.$content.focus(); |
633 |
return this.getReadyProcess( data ).execute().then( function () { |
634 |
// Force redraw by asking the browser to measure the elements' widths |
635 |
win.$element.addClass( 'oo-ui-window-ready' ).width(); |
636 |
win.$content.addClass( 'oo-ui-window-content-ready' ).width(); |
637 |
} ); |
638 |
}; |
639 |
|
640 |
/** |
641 |
* Hold window. |
642 |
* |
643 |
* This is called by OO.ui.WindowManager during window closing (before the animation), and should |
644 |
* not be called directly by other systems. |
645 |
* |
646 |
* @param {Object} [data] Window closing data |
647 |
* @return {jQuery.Promise} Promise resolved when window is held |
648 |
*/ |
649 |
OO.ui.Window.prototype.hold = function ( data ) { |
650 |
var win = this; |
651 |
|
652 |
return this.getHoldProcess( data ).execute().then( function () { |
653 |
// Get the focused element within the window's content |
654 |
var $focus = win.$content.find( OO.ui.Element.static.getDocument( win.$content ).activeElement ); |
655 |
|
656 |
// Blur the focused element |
657 |
if ( $focus.length ) { |
658 |
$focus[ 0 ].blur(); |
659 |
} |
660 |
|
661 |
// Force redraw by asking the browser to measure the elements' widths |
662 |
win.$element.removeClass( 'oo-ui-window-ready oo-ui-window-setup' ).width(); |
663 |
win.$content.removeClass( 'oo-ui-window-content-ready oo-ui-window-content-setup' ).width(); |
664 |
} ); |
665 |
}; |
666 |
|
667 |
/** |
668 |
* Teardown window. |
669 |
* |
670 |
* This is called by OO.ui.WindowManager during window closing (after the animation), and should not be called directly |
671 |
* by other systems. |
672 |
* |
673 |
* @param {Object} [data] Window closing data |
674 |
* @return {jQuery.Promise} Promise resolved when window is torn down |
675 |
*/ |
676 |
OO.ui.Window.prototype.teardown = function ( data ) { |
677 |
var win = this; |
678 |
|
679 |
return this.getTeardownProcess( data ).execute().then( function () { |
680 |
// Force redraw by asking the browser to measure the elements' widths |
681 |
win.$element.removeClass( 'oo-ui-window-active' ).width(); |
682 |
|
683 |
win.$focusTraps.off( 'focus', win.focusTrapHandler ); |
684 |
win.toggle( false ); |
685 |
} ); |
686 |
}; |
687 |
|
|
|
/src/WindowInstance.js
|
4 problems (4 errors, 0 warnings)
|
Line |
Source |
1 |
/** |
2 |
* A window instance represents the life cycle for one single opening of a window |
3 |
* until its closing. |
4 |
* |
5 |
* While OO.ui.WindowManager will reuse OO.ui.Window objects, each time a window is |
6 |
* opened, a new lifecycle starts. |
7 |
* |
8 |
* For more information, please see the [OOUI documentation on MediaWiki] [1]. |
9 |
* |
10 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Windows |
11 |
* |
12 |
* @class |
13 |
* |
14 |
* @constructor |
15 |
*/ |
16 |
OO.ui.WindowInstance = function OoUiWindowInstance() { |
17 |
var deferreds = { |
Error |
Row 18, Column 12: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
18 |
opening: $.Deferred(), |
Error |
Row 19, Column 11: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
19 |
opened: $.Deferred(), |
Error |
Row 20, Column 12: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
20 |
closing: $.Deferred(), |
Error |
Row 21, Column 11: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
21 |
closed: $.Deferred() |
22 |
}; |
23 |
|
24 |
/** |
25 |
* @private |
26 |
* @property {Object} |
27 |
*/ |
28 |
this.deferreds = deferreds; |
29 |
|
30 |
// Set these up as chained promises so that rejecting of |
31 |
// an earlier stage automatically rejects the subsequent |
32 |
// would-be stages as well. |
33 |
|
34 |
/** |
35 |
* @property {jQuery.Promise} |
36 |
*/ |
37 |
this.opening = deferreds.opening.promise(); |
38 |
/** |
39 |
* @property {jQuery.Promise} |
40 |
*/ |
41 |
this.opened = this.opening.then( function () { |
42 |
return deferreds.opened; |
43 |
} ); |
44 |
/** |
45 |
* @property {jQuery.Promise} |
46 |
*/ |
47 |
this.closing = this.opened.then( function () { |
48 |
return deferreds.closing; |
49 |
} ); |
50 |
/** |
51 |
* @property {jQuery.Promise} |
52 |
*/ |
53 |
this.closed = this.closing.then( function () { |
54 |
return deferreds.closed; |
55 |
} ); |
56 |
}; |
57 |
|
58 |
/* Setup */ |
59 |
|
60 |
OO.initClass( OO.ui.WindowInstance ); |
61 |
|
62 |
/** |
63 |
* Check if window is opening. |
64 |
* |
65 |
* @return {boolean} Window is opening |
66 |
*/ |
67 |
OO.ui.WindowInstance.prototype.isOpening = function () { |
68 |
return this.deferreds.opened.state() === 'pending'; |
69 |
}; |
70 |
|
71 |
/** |
72 |
* Check if window is opened. |
73 |
* |
74 |
* @return {boolean} Window is opened |
75 |
*/ |
76 |
OO.ui.WindowInstance.prototype.isOpened = function () { |
77 |
return this.deferreds.opened.state() === 'resolved' && |
78 |
this.deferreds.closing.state() === 'pending'; |
79 |
}; |
80 |
|
81 |
/** |
82 |
* Check if window is closing. |
83 |
* |
84 |
* @return {boolean} Window is closing |
85 |
*/ |
86 |
OO.ui.WindowInstance.prototype.isClosing = function () { |
87 |
return this.deferreds.closing.state() === 'resolved' && |
88 |
this.deferreds.closed.state() === 'pending'; |
89 |
}; |
90 |
|
91 |
/** |
92 |
* Check if window is closed. |
93 |
* |
94 |
* @return {boolean} Window is closed |
95 |
*/ |
96 |
OO.ui.WindowInstance.prototype.isClosed = function () { |
97 |
return this.deferreds.closed.state() === 'resolved'; |
98 |
}; |
99 |
|
|
|
/src/WindowManager.js
|
12 problems (12 errors, 0 warnings)
|
Severity |
Rule |
Error |
Row 310, Column 17: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
Error |
Row 367, Column 35: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
Error |
Row 411, Column 25: "Prefer Promise.all to $.when"
jquery/no-when
|
Error |
Row 425, Column 27: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
Error |
Row 463, Column 19: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
Error |
Row 514, Column 26: "Prefer Promise.all to $.when"
jquery/no-when
|
Error |
Row 707, Column 16: "Prefer WeakMap to $.data"
jquery/no-data
|
Error |
Row 719, Column 30: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 720, Column 5: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 735, Column 4: "Prefer getComputedStyle to $.css"
jquery/no-css
|
Error |
Row 742, Column 2: "Prefer WeakMap to $.data"
jquery/no-data
|
Error |
Row 769, Column 23: "Prefer setAttribute to $.attr"
jquery/no-attr
|
Line |
Source |
1 |
/** |
2 |
* Window managers are used to open and close {@link OO.ui.Window windows} and control their presentation. |
3 |
* Managed windows are mutually exclusive. If a new window is opened while a current window is opening |
4 |
* or is opened, the current window will be closed and any ongoing {@link OO.ui.Process process} will be cancelled. Windows |
5 |
* themselves are persistent and—rather than being torn down when closed—can be repopulated with the |
6 |
* pertinent data and reused. |
7 |
* |
8 |
* Over the lifecycle of a window, the window manager makes available three promises: `opening`, |
9 |
* `opened`, and `closing`, which represent the primary stages of the cycle: |
10 |
* |
11 |
* **Opening**: the opening stage begins when the window manager’s #openWindow or a window’s |
12 |
* {@link OO.ui.Window#open open} method is used, and the window manager begins to open the window. |
13 |
* |
14 |
* - an `opening` event is emitted with an `opening` promise |
15 |
* - the #getSetupDelay method is called and the returned value is used to time a pause in execution before the |
16 |
* window’s {@link OO.ui.Window#method-setup setup} method is called which executes OO.ui.Window#getSetupProcess. |
17 |
* - a `setup` progress notification is emitted from the `opening` promise |
18 |
* - the #getReadyDelay method is called the returned value is used to time a pause in execution before the |
19 |
* window’s {@link OO.ui.Window#method-ready ready} method is called which executes OO.ui.Window#getReadyProcess. |
20 |
* - a `ready` progress notification is emitted from the `opening` promise |
21 |
* - the `opening` promise is resolved with an `opened` promise |
22 |
* |
23 |
* **Opened**: the window is now open. |
24 |
* |
25 |
* **Closing**: the closing stage begins when the window manager's #closeWindow or the |
26 |
* window's {@link OO.ui.Window#close close} methods is used, and the window manager begins |
27 |
* to close the window. |
28 |
* |
29 |
* - the `opened` promise is resolved with `closing` promise and a `closing` event is emitted |
30 |
* - the #getHoldDelay method is called and the returned value is used to time a pause in execution before |
31 |
* the window's {@link OO.ui.Window#getHoldProcess getHoldProcess} method is called on the |
32 |
* window and its result executed |
33 |
* - a `hold` progress notification is emitted from the `closing` promise |
34 |
* - the #getTeardownDelay() method is called and the returned value is used to time a pause in execution before |
35 |
* the window's {@link OO.ui.Window#getTeardownProcess getTeardownProcess} method is called on the |
36 |
* window and its result executed |
37 |
* - a `teardown` progress notification is emitted from the `closing` promise |
38 |
* - the `closing` promise is resolved. The window is now closed |
39 |
* |
40 |
* See the [OOUI documentation on MediaWiki][1] for more information. |
41 |
* |
42 |
* [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers |
43 |
* |
44 |
* @class |
45 |
* @extends OO.ui.Element |
46 |
* @mixins OO.EventEmitter |
47 |
* |
48 |
* @constructor |
49 |
* @param {Object} [config] Configuration options |
50 |
* @cfg {OO.Factory} [factory] Window factory to use for automatic instantiation |
51 |
* Note that window classes that are instantiated with a factory must have |
52 |
* a {@link OO.ui.Dialog#static-name static name} property that specifies a symbolic name. |
53 |
* @cfg {boolean} [modal=true] Prevent interaction outside the dialog |
54 |
*/ |
55 |
OO.ui.WindowManager = function OoUiWindowManager( config ) { |
56 |
// Configuration initialization |
57 |
config = config || {}; |
58 |
|
59 |
// Parent constructor |
60 |
OO.ui.WindowManager.parent.call( this, config ); |
61 |
|
62 |
// Mixin constructors |
63 |
OO.EventEmitter.call( this ); |
64 |
|
65 |
// Properties |
66 |
this.factory = config.factory; |
67 |
this.modal = config.modal === undefined || !!config.modal; |
68 |
this.windows = {}; |
69 |
// Deprecated placeholder promise given to compatOpening in openWindow() |
70 |
// that is resolved in closeWindow(). |
71 |
this.compatOpened = null; |
72 |
this.preparingToOpen = null; |
73 |
this.preparingToClose = null; |
74 |
this.currentWindow = null; |
75 |
this.globalEvents = false; |
76 |
this.$returnFocusTo = null; |
77 |
this.$ariaHidden = null; |
78 |
this.onWindowResizeTimeout = null; |
79 |
this.onWindowResizeHandler = this.onWindowResize.bind( this ); |
80 |
this.afterWindowResizeHandler = this.afterWindowResize.bind( this ); |
81 |
|
82 |
// Initialization |
83 |
this.$element |
84 |
.addClass( 'oo-ui-windowManager' ) |
85 |
.toggleClass( 'oo-ui-windowManager-modal', this.modal ); |
86 |
if ( this.modal ) { |
87 |
this.$element.attr( 'aria-hidden', true ); |
88 |
} |
89 |
}; |
90 |
|
91 |
/* Setup */ |
92 |
|
93 |
OO.inheritClass( OO.ui.WindowManager, OO.ui.Element ); |
94 |
OO.mixinClass( OO.ui.WindowManager, OO.EventEmitter ); |
95 |
|
96 |
/* Events */ |
97 |
|
98 |
/** |
99 |
* An 'opening' event is emitted when the window begins to be opened. |
100 |
* |
101 |
* @event opening |
102 |
* @param {OO.ui.Window} win Window that's being opened |
103 |
* @param {jQuery.Promise} opened A promise resolved with a value when the window is opened successfully. |
104 |
* This promise also emits `setup` and `ready` notifications. When this promise is resolved, the first |
105 |
* argument of the value is an 'closed' promise, the second argument is the opening data. |
106 |
* @param {Object} data Window opening data |
107 |
*/ |
108 |
|
109 |
/** |
110 |
* A 'closing' event is emitted when the window begins to be closed. |
111 |
* |
112 |
* @event closing |
113 |
* @param {OO.ui.Window} win Window that's being closed |
114 |
* @param {jQuery.Promise} closed A promise resolved with a value when the window is closed successfully. |
115 |
* This promise also emits `hold` and `teardown` notifications. When this promise is resolved, the first |
116 |
* argument of its value is the closing data. |
117 |
* @param {Object} data Window closing data |
118 |
*/ |
119 |
|
120 |
/** |
121 |
* A 'resize' event is emitted when a window is resized. |
122 |
* |
123 |
* @event resize |
124 |
* @param {OO.ui.Window} win Window that was resized |
125 |
*/ |
126 |
|
127 |
/* Static Properties */ |
128 |
|
129 |
/** |
130 |
* Map of the symbolic name of each window size and its CSS properties. |
131 |
* |
132 |
* @static |
133 |
* @inheritable |
134 |
* @property {Object} |
135 |
*/ |
136 |
OO.ui.WindowManager.static.sizes = { |
137 |
small: { |
138 |
width: 300 |
139 |
}, |
140 |
medium: { |
141 |
width: 500 |
142 |
}, |
143 |
large: { |
144 |
width: 700 |
145 |
}, |
146 |
larger: { |
147 |
width: 900 |
148 |
}, |
149 |
full: { |
150 |
// These can be non-numeric because they are never used in calculations |
151 |
width: '100%', |
152 |
height: '100%' |
153 |
} |
154 |
}; |
155 |
|
156 |
/** |
157 |
* Symbolic name of the default window size. |
158 |
* |
159 |
* The default size is used if the window's requested size is not recognized. |
160 |
* |
161 |
* @static |
162 |
* @inheritable |
163 |
* @property {string} |
164 |
*/ |
165 |
OO.ui.WindowManager.static.defaultSize = 'medium'; |
166 |
|
167 |
/* Methods */ |
168 |
|
169 |
/** |
170 |
* Handle window resize events. |
171 |
* |
172 |
* @private |
173 |
* @param {jQuery.Event} e Window resize event |
174 |
*/ |
175 |
OO.ui.WindowManager.prototype.onWindowResize = function () { |
176 |
clearTimeout( this.onWindowResizeTimeout ); |
177 |
this.onWindowResizeTimeout = setTimeout( this.afterWindowResizeHandler, 200 ); |
178 |
}; |
179 |
|
180 |
/** |
181 |
* Handle window resize events. |
182 |
* |
183 |
* @private |
184 |
* @param {jQuery.Event} e Window resize event |
185 |
*/ |
186 |
OO.ui.WindowManager.prototype.afterWindowResize = function () { |
187 |
var currentFocusedElement = document.activeElement; |
188 |
if ( this.currentWindow ) { |
189 |
this.updateWindowSize( this.currentWindow ); |
190 |
|
191 |
// Restore focus to the original element if it has changed. |
192 |
// When a layout change is made on resize inputs lose focus |
193 |
// on Android (Chrome and Firefox). See T162127. |
194 |
if ( currentFocusedElement !== document.activeElement ) { |
195 |
currentFocusedElement.focus(); |
196 |
} |
197 |
} |
198 |
}; |
199 |
|
200 |
/** |
201 |
* Check if window is opening. |
202 |
* |
203 |
* @param {OO.ui.Window} win Window to check |
204 |
* @return {boolean} Window is opening |
205 |
*/ |
206 |
OO.ui.WindowManager.prototype.isOpening = function ( win ) { |
207 |
return win === this.currentWindow && !!this.lifecycle && |
208 |
this.lifecycle.isOpening(); |
209 |
}; |
210 |
|
211 |
/** |
212 |
* Check if window is closing. |
213 |
* |
214 |
* @param {OO.ui.Window} win Window to check |
215 |
* @return {boolean} Window is closing |
216 |
*/ |
217 |
OO.ui.WindowManager.prototype.isClosing = function ( win ) { |
218 |
return win === this.currentWindow && !!this.lifecycle && |
219 |
this.lifecycle.isClosing(); |
220 |
}; |
221 |
|
222 |
/** |
223 |
* Check if window is opened. |
224 |
* |
225 |
* @param {OO.ui.Window} win Window to check |
226 |
* @return {boolean} Window is opened |
227 |
*/ |
228 |
OO.ui.WindowManager.prototype.isOpened = function ( win ) { |
229 |
return win === this.currentWindow && !!this.lifecycle && |
230 |
this.lifecycle.isOpened(); |
231 |
}; |
232 |
|
233 |
/** |
234 |
* Check if a window is being managed. |
235 |
* |
236 |
* @param {OO.ui.Window} win Window to check |
237 |
* @return {boolean} Window is being managed |
238 |
*/ |
239 |
OO.ui.WindowManager.prototype.hasWindow = function ( win ) { |
240 |
var name; |
241 |
|
242 |
for ( name in this.windows ) { |
243 |
if ( this.windows[ name ] === win ) { |
244 |
return true; |
245 |
} |
246 |
} |
247 |
|
248 |
return false; |
249 |
}; |
250 |
|
251 |
/** |
252 |
* Get the number of milliseconds to wait after opening begins before executing the ‘setup’ process. |
253 |
* |
254 |
* @param {OO.ui.Window} win Window being opened |
255 |
* @param {Object} [data] Window opening data |
256 |
* @return {number} Milliseconds to wait |
257 |
*/ |
258 |
OO.ui.WindowManager.prototype.getSetupDelay = function () { |
259 |
return 0; |
260 |
}; |
261 |
|
262 |
/** |
263 |
* Get the number of milliseconds to wait after setup has finished before executing the ‘ready’ process. |
264 |
* |
265 |
* @param {OO.ui.Window} win Window being opened |
266 |
* @param {Object} [data] Window opening data |
267 |
* @return {number} Milliseconds to wait |
268 |
*/ |
269 |
OO.ui.WindowManager.prototype.getReadyDelay = function () { |
270 |
return this.modal ? OO.ui.theme.getDialogTransitionDuration() : 0; |
271 |
}; |
272 |
|
273 |
/** |
274 |
* Get the number of milliseconds to wait after closing has begun before executing the 'hold' process. |
275 |
* |
276 |
* @param {OO.ui.Window} win Window being closed |
277 |
* @param {Object} [data] Window closing data |
278 |
* @return {number} Milliseconds to wait |
279 |
*/ |
280 |
OO.ui.WindowManager.prototype.getHoldDelay = function () { |
281 |
return 0; |
282 |
}; |
283 |
|
284 |
/** |
285 |
* Get the number of milliseconds to wait after the ‘hold’ process has finished before |
286 |
* executing the ‘teardown’ process. |
287 |
* |
288 |
* @param {OO.ui.Window} win Window being closed |
289 |
* @param {Object} [data] Window closing data |
290 |
* @return {number} Milliseconds to wait |
291 |
*/ |
292 |
OO.ui.WindowManager.prototype.getTeardownDelay = function () { |
293 |
return this.modal ? OO.ui.theme.getDialogTransitionDuration() : 0; |
294 |
}; |
295 |
|
296 |
/** |
297 |
* Get a window by its symbolic name. |
298 |
* |
299 |
* If the window is not yet instantiated and its symbolic name is recognized by a factory, it will be |
300 |
* instantiated and added to the window manager automatically. Please see the [OOUI documentation on MediaWiki][3] |
301 |
* for more information about using factories. |
302 |
* [3]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers |
303 |
* |
304 |
* @param {string} name Symbolic name of the window |
305 |
* @return {jQuery.Promise} Promise resolved with matching window, or rejected with an OO.ui.Error |
306 |
* @throws {Error} An error is thrown if the symbolic name is not recognized by the factory. |
307 |
* @throws {Error} An error is thrown if the named window is not recognized as a managed window. |
308 |
*/ |
309 |
OO.ui.WindowManager.prototype.getWindow = function ( name ) { |
Error |
Row 310, Column 17: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
310 |
var deferred = $.Deferred(), |
311 |
win = this.windows[ name ]; |
312 |
|
313 |
if ( !( win instanceof OO.ui.Window ) ) { |
314 |
if ( this.factory ) { |
315 |
if ( !this.factory.lookup( name ) ) { |
316 |
deferred.reject( new OO.ui.Error( |
317 |
'Cannot auto-instantiate window: symbolic name is unrecognized by the factory' |
318 |
) ); |
319 |
} else { |
320 |
win = this.factory.create( name ); |
321 |
this.addWindows( [ win ] ); |
322 |
deferred.resolve( win ); |
323 |
} |
324 |
} else { |
325 |
deferred.reject( new OO.ui.Error( |
326 |
'Cannot get unmanaged window: symbolic name unrecognized as a managed window' |
327 |
) ); |
328 |
} |
329 |
} else { |
330 |
deferred.resolve( win ); |
331 |
} |
332 |
|
333 |
return deferred.promise(); |
334 |
}; |
335 |
|
336 |
/** |
337 |
* Get current window. |
338 |
* |
339 |
* @return {OO.ui.Window|null} Currently opening/opened/closing window |
340 |
*/ |
341 |
OO.ui.WindowManager.prototype.getCurrentWindow = function () { |
342 |
return this.currentWindow; |
343 |
}; |
344 |
|
345 |
/** |
346 |
* Open a window. |
347 |
* |
348 |
* @param {OO.ui.Window|string} win Window object or symbolic name of window to open |
349 |
* @param {Object} [data] Window opening data |
350 |
* @param {jQuery|null} [data.$returnFocusTo] Element to which the window will return focus when closed. |
351 |
* Defaults the current activeElement. If set to null, focus isn't changed on close. |
352 |
* @return {OO.ui.WindowInstance} A lifecycle object representing this particular |
353 |
* opening of the window. For backwards-compatibility, then object is also a Thenable that is resolved |
354 |
* when the window is done opening, with nested promise for when closing starts. This behaviour |
355 |
* is deprecated and is not compatible with jQuery 3. See T163510. |
356 |
* @fires opening |
357 |
*/ |
358 |
OO.ui.WindowManager.prototype.openWindow = function ( win, data, lifecycle, compatOpening ) { |
359 |
var error, |
360 |
manager = this; |
361 |
data = data || {}; |
362 |
|
363 |
// Internal parameter 'lifecycle' allows this method to always return |
364 |
// a lifecycle even if the window still needs to be created |
365 |
// asynchronously when 'win' is a string. |
366 |
lifecycle = lifecycle || new OO.ui.WindowInstance(); |
Error |
Row 367, Column 35: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
367 |
compatOpening = compatOpening || $.Deferred(); |
368 |
|
369 |
// Turn lifecycle into a Thenable for backwards-compatibility with |
370 |
// the deprecated nested-promise behaviour, see T163510. |
371 |
[ 'state', 'always', 'catch', 'pipe', 'then', 'promise', 'progress', 'done', 'fail' ] |
372 |
.forEach( function ( method ) { |
373 |
lifecycle[ method ] = function () { |
374 |
OO.ui.warnDeprecation( |
375 |
'Using the return value of openWindow as a promise is deprecated. ' + |
376 |
'Use .openWindow( ... ).opening.' + method + '( ... ) instead.' |
377 |
); |
378 |
return compatOpening[ method ].apply( this, arguments ); |
379 |
}; |
380 |
} ); |
381 |
|
382 |
// Argument handling |
383 |
if ( typeof win === 'string' ) { |
384 |
this.getWindow( win ).then( |
385 |
function ( win ) { |
386 |
manager.openWindow( win, data, lifecycle, compatOpening ); |
387 |
}, |
388 |
function ( err ) { |
389 |
lifecycle.deferreds.opening.reject( err ); |
390 |
} |
391 |
); |
392 |
return lifecycle; |
393 |
} |
394 |
|
395 |
// Error handling |
396 |
if ( !this.hasWindow( win ) ) { |
397 |
error = 'Cannot open window: window is not attached to manager'; |
398 |
} else if ( this.lifecycle && this.lifecycle.isOpened() ) { |
399 |
error = 'Cannot open window: another window is open'; |
400 |
} else if ( this.preparingToOpen || ( this.lifecycle && this.lifecycle.isOpening() ) ) { |
401 |
error = 'Cannot open window: another window is opening'; |
402 |
} |
403 |
|
404 |
if ( error ) { |
405 |
compatOpening.reject( new OO.ui.Error( error ) ); |
406 |
lifecycle.deferreds.opening.reject( new OO.ui.Error( error ) ); |
407 |
return lifecycle; |
408 |
} |
409 |
|
410 |
// If a window is currently closing, wait for it to complete |
Error |
Row 411, Column 25: "Prefer Promise.all to $.when"
jquery/no-when
|
411 |
this.preparingToOpen = $.when( this.lifecycle && this.lifecycle.closed ); |
412 |
// Ensure handlers get called after preparingToOpen is set |
413 |
this.preparingToOpen.done( function () { |
414 |
if ( manager.modal ) { |
415 |
manager.toggleGlobalEvents( true ); |
416 |
manager.toggleAriaIsolation( true ); |
417 |
} |
418 |
manager.$returnFocusTo = data.$returnFocusTo !== undefined ? data.$returnFocusTo : $( document.activeElement ); |
419 |
manager.currentWindow = win; |
420 |
manager.lifecycle = lifecycle; |
421 |
manager.preparingToOpen = null; |
422 |
manager.emit( 'opening', win, compatOpening, data ); |
423 |
lifecycle.deferreds.opening.resolve( data ); |
424 |
setTimeout( function () { |
Error |
Row 425, Column 27: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
425 |
manager.compatOpened = $.Deferred(); |
426 |
win.setup( data ).then( function () { |
427 |
compatOpening.notify( { state: 'setup' } ); |
428 |
setTimeout( function () { |
429 |
win.ready( data ).then( function () { |
430 |
compatOpening.notify( { state: 'ready' } ); |
431 |
lifecycle.deferreds.opened.resolve( data ); |
432 |
compatOpening.resolve( manager.compatOpened.promise(), data ); |
433 |
}, function () { |
434 |
lifecycle.deferreds.opened.reject(); |
435 |
compatOpening.reject(); |
436 |
manager.closeWindow( win ); |
437 |
} ); |
438 |
}, manager.getReadyDelay() ); |
439 |
}, function () { |
440 |
lifecycle.deferreds.opened.reject(); |
441 |
compatOpening.reject(); |
442 |
manager.closeWindow( win ); |
443 |
} ); |
444 |
}, manager.getSetupDelay() ); |
445 |
} ); |
446 |
|
447 |
return lifecycle; |
448 |
}; |
449 |
|
450 |
/** |
451 |
* Close a window. |
452 |
* |
453 |
* @param {OO.ui.Window|string} win Window object or symbolic name of window to close |
454 |
* @param {Object} [data] Window closing data |
455 |
* @return {OO.ui.WindowInstance} A lifecycle object representing this particular |
456 |
* opening of the window. For backwards-compatibility, the object is also a Thenable that is resolved |
457 |
* when the window is done closing, see T163510. |
458 |
* @fires closing |
459 |
*/ |
460 |
OO.ui.WindowManager.prototype.closeWindow = function ( win, data ) { |
461 |
var error, |
462 |
manager = this, |
Error |
Row 463, Column 19: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
463 |
compatClosing = $.Deferred(), |
464 |
lifecycle = this.lifecycle, |
465 |
compatOpened; |
466 |
|
467 |
// Argument handling |
468 |
if ( typeof win === 'string' ) { |
469 |
win = this.windows[ win ]; |
470 |
} else if ( !this.hasWindow( win ) ) { |
471 |
win = null; |
472 |
} |
473 |
|
474 |
// Error handling |
475 |
if ( !lifecycle ) { |
476 |
error = 'Cannot close window: no window is currently open'; |
477 |
} else if ( !win ) { |
478 |
error = 'Cannot close window: window is not attached to manager'; |
479 |
} else if ( win !== this.currentWindow || this.lifecycle.isClosed() ) { |
480 |
error = 'Cannot close window: window already closed with different data'; |
481 |
} else if ( this.preparingToClose || this.lifecycle.isClosing() ) { |
482 |
error = 'Cannot close window: window already closing with different data'; |
483 |
} |
484 |
|
485 |
if ( error ) { |
486 |
// This function was called for the wrong window and we don't want to mess with the current |
487 |
// window's state. |
488 |
lifecycle = new OO.ui.WindowInstance(); |
489 |
// Pretend the window has been opened, so that we can pretend to fail to close it. |
490 |
lifecycle.deferreds.opening.resolve( {} ); |
491 |
lifecycle.deferreds.opened.resolve( {} ); |
492 |
} |
493 |
|
494 |
// Turn lifecycle into a Thenable for backwards-compatibility with |
495 |
// the deprecated nested-promise behaviour, see T163510. |
496 |
[ 'state', 'always', 'catch', 'pipe', 'then', 'promise', 'progress', 'done', 'fail' ] |
497 |
.forEach( function ( method ) { |
498 |
lifecycle[ method ] = function () { |
499 |
OO.ui.warnDeprecation( |
500 |
'Using the return value of closeWindow as a promise is deprecated. ' + |
501 |
'Use .closeWindow( ... ).closed.' + method + '( ... ) instead.' |
502 |
); |
503 |
return compatClosing[ method ].apply( this, arguments ); |
504 |
}; |
505 |
} ); |
506 |
|
507 |
if ( error ) { |
508 |
compatClosing.reject( new OO.ui.Error( error ) ); |
509 |
lifecycle.deferreds.closing.reject( new OO.ui.Error( error ) ); |
510 |
return lifecycle; |
511 |
} |
512 |
|
513 |
// If the window is currently opening, close it when it's done |
Error |
Row 514, Column 26: "Prefer Promise.all to $.when"
jquery/no-when
|
514 |
this.preparingToClose = $.when( this.lifecycle.opened ); |
515 |
// Ensure handlers get called after preparingToClose is set |
516 |
this.preparingToClose.always( function () { |
517 |
manager.preparingToClose = null; |
518 |
manager.emit( 'closing', win, compatClosing, data ); |
519 |
lifecycle.deferreds.closing.resolve( data ); |
520 |
compatOpened = manager.compatOpened; |
521 |
manager.compatOpened = null; |
522 |
compatOpened.resolve( compatClosing.promise(), data ); |
523 |
setTimeout( function () { |
524 |
win.hold( data ).then( function () { |
525 |
compatClosing.notify( { state: 'hold' } ); |
526 |
setTimeout( function () { |
527 |
win.teardown( data ).then( function () { |
528 |
compatClosing.notify( { state: 'teardown' } ); |
529 |
if ( manager.modal ) { |
530 |
manager.toggleGlobalEvents( false ); |
531 |
manager.toggleAriaIsolation( false ); |
532 |
} |
533 |
if ( manager.$returnFocusTo && manager.$returnFocusTo.length ) { |
534 |
manager.$returnFocusTo[ 0 ].focus(); |
535 |
} |
536 |
manager.currentWindow = null; |
537 |
manager.lifecycle = null; |
538 |
lifecycle.deferreds.closed.resolve( data ); |
539 |
compatClosing.resolve( data ); |
540 |
} ); |
541 |
}, manager.getTeardownDelay() ); |
542 |
} ); |
543 |
}, manager.getHoldDelay() ); |
544 |
} ); |
545 |
|
546 |
return lifecycle; |
547 |
}; |
548 |
|
549 |
/** |
550 |
* Add windows to the window manager. |
551 |
* |
552 |
* Windows can be added by reference, symbolic name, or explicitly defined symbolic names. |
553 |
* See the [OOUI documentation on MediaWiki] [2] for examples. |
554 |
* [2]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers |
555 |
* |
556 |
* This function can be called in two manners: |
557 |
* |
558 |
* 1. `.addWindows( [ windowA, windowB, ... ] )` (where `windowA`, `windowB` are OO.ui.Window objects) |
559 |
* |
560 |
* This syntax registers windows under the symbolic names defined in their `.static.name` |
561 |
* properties. For example, if `windowA.constructor.static.name` is `'nameA'`, calling |
562 |
* `.openWindow( 'nameA' )` afterwards will open the window `windowA`. This syntax requires the |
563 |
* static name to be set, otherwise an exception will be thrown. |
564 |
* |
565 |
* This is the recommended way, as it allows for an easier switch to using a window factory. |
566 |
* |
567 |
* 2. `.addWindows( { nameA: windowA, nameB: windowB, ... } )` |
568 |
* |
569 |
* This syntax registers windows under the explicitly given symbolic names. In this example, |
570 |
* calling `.openWindow( 'nameA' )` afterwards will open the window `windowA`, regardless of what |
571 |
* its `.static.name` is set to. The static name is not required to be set. |
572 |
* |
573 |
* This should only be used if you need to override the default symbolic names. |
574 |
* |
575 |
* Example: |
576 |
* |
577 |
* var windowManager = new OO.ui.WindowManager(); |
578 |
* $( 'body' ).append( windowManager.$element ); |
579 |
* |
580 |
* // Add a window under the default name: see OO.ui.MessageDialog.static.name |
581 |
* windowManager.addWindows( [ new OO.ui.MessageDialog() ] ); |
582 |
* // Add a window under an explicit name |
583 |
* windowManager.addWindows( { myMessageDialog: new OO.ui.MessageDialog() } ); |
584 |
* |
585 |
* // Open window by default name |
586 |
* windowManager.openWindow( 'message' ); |
587 |
* // Open window by explicitly given name |
588 |
* windowManager.openWindow( 'myMessageDialog' ); |
589 |
* |
590 |
* |
591 |
* @param {Object.<string,OO.ui.Window>|OO.ui.Window[]} windows An array of window objects specified |
592 |
* by reference, symbolic name, or explicitly defined symbolic names. |
593 |
* @throws {Error} An error is thrown if a window is added by symbolic name, but has neither an |
594 |
* explicit nor a statically configured symbolic name. |
595 |
*/ |
596 |
OO.ui.WindowManager.prototype.addWindows = function ( windows ) { |
597 |
var i, len, win, name, list; |
598 |
|
599 |
if ( Array.isArray( windows ) ) { |
600 |
// Convert to map of windows by looking up symbolic names from static configuration |
601 |
list = {}; |
602 |
for ( i = 0, len = windows.length; i < len; i++ ) { |
603 |
name = windows[ i ].constructor.static.name; |
604 |
if ( !name ) { |
605 |
throw new Error( 'Windows must have a `name` static property defined.' ); |
606 |
} |
607 |
list[ name ] = windows[ i ]; |
608 |
} |
609 |
} else if ( OO.isPlainObject( windows ) ) { |
610 |
list = windows; |
611 |
} |
612 |
|
613 |
// Add windows |
614 |
for ( name in list ) { |
615 |
win = list[ name ]; |
616 |
this.windows[ name ] = win.toggle( false ); |
617 |
this.$element.append( win.$element ); |
618 |
win.setManager( this ); |
619 |
} |
620 |
}; |
621 |
|
622 |
/** |
623 |
* Remove the specified windows from the windows manager. |
624 |
* |
625 |
* Windows will be closed before they are removed. If you wish to remove all windows, you may wish to use |
626 |
* the #clearWindows method instead. If you no longer need the window manager and want to ensure that it no |
627 |
* longer listens to events, use the #destroy method. |
628 |
* |
629 |
* @param {string[]} names Symbolic names of windows to remove |
630 |
* @return {jQuery.Promise} Promise resolved when window is closed and removed |
631 |
* @throws {Error} An error is thrown if the named windows are not managed by the window manager. |
632 |
*/ |
633 |
OO.ui.WindowManager.prototype.removeWindows = function ( names ) { |
634 |
var i, len, win, name, cleanupWindow, |
635 |
manager = this, |
636 |
promises = [], |
637 |
cleanup = function ( name, win ) { |
638 |
delete manager.windows[ name ]; |
639 |
win.$element.detach(); |
640 |
}; |
641 |
|
642 |
for ( i = 0, len = names.length; i < len; i++ ) { |
643 |
name = names[ i ]; |
644 |
win = this.windows[ name ]; |
645 |
if ( !win ) { |
646 |
throw new Error( 'Cannot remove window' ); |
647 |
} |
648 |
cleanupWindow = cleanup.bind( null, name, win ); |
649 |
promises.push( this.closeWindow( name ).closed.then( cleanupWindow, cleanupWindow ) ); |
650 |
} |
651 |
|
652 |
return $.when.apply( $, promises ); |
653 |
}; |
654 |
|
655 |
/** |
656 |
* Remove all windows from the window manager. |
657 |
* |
658 |
* Windows will be closed before they are removed. Note that the window manager, though not in use, will still |
659 |
* listen to events. If the window manager will not be used again, you may wish to use the #destroy method instead. |
660 |
* To remove just a subset of windows, use the #removeWindows method. |
661 |
* |
662 |
* @return {jQuery.Promise} Promise resolved when all windows are closed and removed |
663 |
*/ |
664 |
OO.ui.WindowManager.prototype.clearWindows = function () { |
665 |
return this.removeWindows( Object.keys( this.windows ) ); |
666 |
}; |
667 |
|
668 |
/** |
669 |
* Set dialog size. In general, this method should not be called directly. |
670 |
* |
671 |
* Fullscreen mode will be used if the dialog is too wide to fit in the screen. |
672 |
* |
673 |
* @param {OO.ui.Window} win Window to update, should be the current window |
674 |
* @chainable |
675 |
*/ |
676 |
OO.ui.WindowManager.prototype.updateWindowSize = function ( win ) { |
677 |
var isFullscreen; |
678 |
|
679 |
// Bypass for non-current, and thus invisible, windows |
680 |
if ( win !== this.currentWindow ) { |
681 |
return; |
682 |
} |
683 |
|
684 |
isFullscreen = win.getSize() === 'full'; |
685 |
|
686 |
this.$element.toggleClass( 'oo-ui-windowManager-fullscreen', isFullscreen ); |
687 |
this.$element.toggleClass( 'oo-ui-windowManager-floating', !isFullscreen ); |
688 |
win.setDimensions( win.getSizeProperties() ); |
689 |
|
690 |
this.emit( 'resize', win ); |
691 |
|
692 |
return this; |
693 |
}; |
694 |
|
695 |
/** |
696 |
* Bind or unbind global events for scrolling. |
697 |
* |
698 |
* @private |
699 |
* @param {boolean} [on] Bind global events |
700 |
* @chainable |
701 |
*/ |
702 |
OO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) { |
703 |
var scrollWidth, bodyMargin, |
704 |
$body = $( this.getElementDocument().body ), |
705 |
// We could have multiple window managers open so only modify |
706 |
// the body css at the bottom of the stack |
Error |
Row 707, Column 16: "Prefer WeakMap to $.data"
jquery/no-data
|
707 |
stackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0; |
708 |
|
709 |
on = on === undefined ? !!this.globalEvents : !!on; |
710 |
|
711 |
if ( on ) { |
712 |
if ( !this.globalEvents ) { |
713 |
$( this.getElementWindow() ).on( { |
714 |
// Start listening for top-level window dimension changes |
715 |
'orientationchange resize': this.onWindowResizeHandler |
716 |
} ); |
717 |
if ( stackDepth === 0 ) { |
718 |
scrollWidth = window.innerWidth - document.documentElement.clientWidth; |
Error |
Row 719, Column 30: "Prefer getComputedStyle to $.css"
jquery/no-css
|
719 |
bodyMargin = parseFloat( $body.css( 'margin-right' ) ) || 0; |
Error |
Row 720, Column 5: "Prefer getComputedStyle to $.css"
jquery/no-css
|
720 |
$body.css( { |
721 |
overflow: 'hidden', |
722 |
'margin-right': bodyMargin + scrollWidth |
723 |
} ); |
724 |
} |
725 |
stackDepth++; |
726 |
this.globalEvents = true; |
727 |
} |
728 |
} else if ( this.globalEvents ) { |
729 |
$( this.getElementWindow() ).off( { |
730 |
// Stop listening for top-level window dimension changes |
731 |
'orientationchange resize': this.onWindowResizeHandler |
732 |
} ); |
733 |
stackDepth--; |
734 |
if ( stackDepth === 0 ) { |
Error |
Row 735, Column 4: "Prefer getComputedStyle to $.css"
jquery/no-css
|
735 |
$body.css( { |
736 |
overflow: '', |
737 |
'margin-right': '' |
738 |
} ); |
739 |
} |
740 |
this.globalEvents = false; |
741 |
} |
Error |
Row 742, Column 2: "Prefer WeakMap to $.data"
jquery/no-data
|
742 |
$body.data( 'windowManagerGlobalEvents', stackDepth ); |
743 |
|
744 |
return this; |
745 |
}; |
746 |
|
747 |
/** |
748 |
* Toggle screen reader visibility of content other than the window manager. |
749 |
* |
750 |
* @private |
751 |
* @param {boolean} [isolate] Make only the window manager visible to screen readers |
752 |
* @chainable |
753 |
*/ |
754 |
OO.ui.WindowManager.prototype.toggleAriaIsolation = function ( isolate ) { |
755 |
var $topLevelElement; |
756 |
isolate = isolate === undefined ? !this.$ariaHidden : !!isolate; |
757 |
|
758 |
if ( isolate ) { |
759 |
if ( !this.$ariaHidden ) { |
760 |
// Find the top level element containing the window manager or the |
761 |
// window manager's element itself in case its a direct child of body |
762 |
$topLevelElement = this.$element.parentsUntil( 'body' ).last(); |
763 |
$topLevelElement = $topLevelElement.length === 0 ? this.$element : $topLevelElement; |
764 |
|
765 |
// In case previously set by another window manager |
766 |
this.$element.removeAttr( 'aria-hidden' ); |
767 |
|
768 |
// Hide everything other than the window manager from screen readers |
Error |
Row 769, Column 23: "Prefer setAttribute to $.attr"
jquery/no-attr
|
769 |
this.$ariaHidden = $( 'body' ) |
770 |
.children() |
771 |
.not( 'script' ) |
772 |
.not( $topLevelElement ) |
773 |
.attr( 'aria-hidden', true ); |
774 |
} |
775 |
} else if ( this.$ariaHidden ) { |
776 |
// Restore screen reader visibility |
777 |
this.$ariaHidden.removeAttr( 'aria-hidden' ); |
778 |
this.$ariaHidden = null; |
779 |
|
780 |
// and hide the window manager |
781 |
this.$element.attr( 'aria-hidden', true ); |
782 |
} |
783 |
|
784 |
return this; |
785 |
}; |
786 |
|
787 |
/** |
788 |
* Destroy the window manager. |
789 |
* |
790 |
* Destroying the window manager ensures that it will no longer listen to events. If you would like to |
791 |
* continue using the window manager, but wish to remove all windows from it, use the #clearWindows method |
792 |
* instead. |
793 |
*/ |
794 |
OO.ui.WindowManager.prototype.destroy = function () { |
795 |
this.toggleGlobalEvents( false ); |
796 |
this.toggleAriaIsolation( false ); |
797 |
this.clearWindows(); |
798 |
this.$element.remove(); |
799 |
}; |
800 |
|
|
|
/src/windows.js
|
0 problems
|
|
/tests/config.js
|
0 problems
|
|
/tests/core.test.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 91, Column 60: "Prefer querySelectorAll to $.find"
jquery/no-find
|
Line |
Source |
1 |
QUnit.module( 'core' ); |
2 |
|
3 |
/** |
4 |
* @note: Keep tests in sync with phpunit/TagTest.php |
5 |
*/ |
6 |
QUnit.test( 'isSafeUrl', function ( assert ) { |
7 |
// eslint-disable-next-line no-script-url |
8 |
assert.strictEqual( OO.ui.isSafeUrl( 'javascript:evil();' ), false ); |
9 |
assert.strictEqual( OO.ui.isSafeUrl( 'foo:bar' ), false ); |
10 |
assert.strictEqual( OO.ui.isSafeUrl( 'relative.html' ), false ); |
11 |
assert.strictEqual( OO.ui.isSafeUrl( '' ), true ); |
12 |
assert.strictEqual( OO.ui.isSafeUrl( 'http://example.com/' ), true ); |
13 |
assert.strictEqual( OO.ui.isSafeUrl( '//example.com/' ), true ); |
14 |
assert.strictEqual( OO.ui.isSafeUrl( '/' ), true ); |
15 |
assert.strictEqual( OO.ui.isSafeUrl( '..' ), false ); |
16 |
assert.strictEqual( OO.ui.isSafeUrl( '?foo=bar' ), true ); |
17 |
assert.strictEqual( OO.ui.isSafeUrl( '#top' ), true ); |
18 |
assert.strictEqual( OO.ui.isSafeUrl( '/relative' ), true ); |
19 |
assert.strictEqual( OO.ui.isSafeUrl( './relative' ), true ); |
20 |
assert.strictEqual( OO.ui.isSafeUrl( '/wiki/Extra:Colon' ), true ); |
21 |
} ); |
22 |
|
23 |
// eslint-disable-next-line qunit/require-expect |
24 |
QUnit.test( 'isFocusableElement', function ( assert ) { |
25 |
var i, $html, result, |
26 |
cases = [ |
27 |
{ |
28 |
msg: 'Plain DIV', |
29 |
html: '<div>foo</div>', |
30 |
expected: false |
31 |
}, |
32 |
{ |
33 |
msg: 'Tabindexed span', |
34 |
html: '<span tabindex="3">foo</span>', |
35 |
expected: true |
36 |
}, |
37 |
{ |
38 |
msg: 'Hidden element', |
39 |
html: '<div tabindex="3" style="display:none;">foo</div>', |
40 |
expected: false |
41 |
}, |
42 |
{ |
43 |
msg: 'Invalid tabindex', |
44 |
html: '<div tabindex="wat">foo</div>', |
45 |
expected: false |
46 |
}, |
47 |
{ |
48 |
msg: 'Empty tabindex', |
49 |
html: '<div tabindex="">foo</div>', |
50 |
expected: false |
51 |
}, |
52 |
{ |
53 |
msg: 'Text input', |
54 |
html: '<input type="text">', |
55 |
expected: true |
56 |
}, |
57 |
{ |
58 |
msg: 'Disabled text input', |
59 |
html: '<input type="text" disabled="disabled">', |
60 |
expected: false |
61 |
}, |
62 |
{ |
63 |
msg: 'Link', |
64 |
html: '<a href="Foo">Bar</a>', |
65 |
expected: true |
66 |
}, |
67 |
{ |
68 |
msg: 'Link with empty href', |
69 |
html: '<a href="">Bar</a>', |
70 |
expected: true |
71 |
}, |
72 |
{ |
73 |
msg: 'Link without href', |
74 |
html: '<a>Bar</a>', |
75 |
expected: false |
76 |
}, |
77 |
{ |
78 |
msg: 'Link without href but with tabindex', |
79 |
html: '<a tabindex="0">Bar</a>', |
80 |
expected: true |
81 |
}, |
82 |
{ |
83 |
msg: 'Link with negative tabindex', |
84 |
html: '<a href="foo" tabindex="-1">Bar</a>', |
85 |
expected: true |
86 |
} |
87 |
]; |
88 |
assert.expect( cases.length ); |
89 |
for ( i = 0; i < cases.length; i++ ) { |
90 |
$html = $( cases[ i ].html ).appendTo( 'body' ); |
Error |
Row 91, Column 60: "Prefer querySelectorAll to $.find"
jquery/no-find
|
91 |
result = OO.ui.isFocusableElement( cases[ i ].selector ? $html.find( cases[ i ].selector ) : $html ); |
92 |
assert.strictEqual( result, cases[ i ].expected, cases[ i ].msg ); |
93 |
$html.remove(); |
94 |
} |
95 |
} ); |
96 |
|
97 |
QUnit.test( 'debounce', function ( assert ) { |
98 |
var f, |
99 |
log = [], |
100 |
testTimer = new OO.ui.TestTimer(), |
101 |
setTimeoutReal = window.setTimeout, |
102 |
clearTimeoutReal = window.clearTimeout; |
103 |
window.setTimeout = testTimer.setTimeout.bind( testTimer ); |
104 |
window.clearTimeout = testTimer.clearTimeout.bind( testTimer ); |
105 |
try { |
106 |
f = OO.ui.debounce( log.push.bind( log ), 50 ); |
107 |
f( 1 ); |
108 |
testTimer.runPending( 20 ); |
109 |
log.push( 'a' ); |
110 |
f( 2 ); |
111 |
testTimer.runPending( 20 ); |
112 |
log.push( 'b' ); |
113 |
f( 3 ); |
114 |
testTimer.runPending( 20 ); |
115 |
log.push( 'c' ); |
116 |
f( 4 ); |
117 |
testTimer.runPending( 20 ); |
118 |
log.push( 'd' ); |
119 |
testTimer.runPending( 20 ); |
120 |
log.push( 'e' ); |
121 |
testTimer.runPending( 20 ); |
122 |
log.push( 'f' ); |
123 |
testTimer.runPending( 20 ); |
124 |
assert.deepEqual( log, [ 'a', 'b', 'c', 'd', 'e', 4, 'f' ], 'debounce 50 ms' ); |
125 |
|
126 |
log = []; |
127 |
f = OO.ui.debounce( log.push.bind( log ), 50, true ); |
128 |
f( 1 ); |
129 |
testTimer.runPending( 20 ); |
130 |
log.push( 'a' ); |
131 |
f( 2 ); |
132 |
testTimer.runPending( 20 ); |
133 |
log.push( 'b' ); |
134 |
f( 3 ); |
135 |
testTimer.runPending( 20 ); |
136 |
log.push( 'c' ); |
137 |
f( 4 ); |
138 |
testTimer.runPending( 20 ); |
139 |
log.push( 'd' ); |
140 |
testTimer.runPending( 20 ); |
141 |
log.push( 'e' ); |
142 |
testTimer.runPending( 20 ); |
143 |
log.push( 'f' ); |
144 |
testTimer.runPending( 20 ); |
145 |
assert.deepEqual( log, [ 1, 'a', 'b', 'c', 'd', 'e', 'f' ], 'debounce 50 ms immediate' ); |
146 |
|
147 |
log = []; |
148 |
f = OO.ui.debounce( log.push.bind( log ), 0 ); |
149 |
f( 1 ); |
150 |
log.push( 'a' ); |
151 |
f( 2 ); |
152 |
log.push( 'b' ); |
153 |
testTimer.runPending(); |
154 |
f( 3 ); |
155 |
log.push( 'c' ); |
156 |
testTimer.runPending(); |
157 |
log.push( 'd' ); |
158 |
assert.deepEqual( log, [ 'a', 'b', 1, 'c', 3, 'd' ], 'debounce 0 ms' ); |
159 |
testTimer.runPending(); |
160 |
|
161 |
log = []; |
162 |
f = OO.ui.debounce( log.push.bind( log ), 0, true ); |
163 |
f( 1 ); |
164 |
log.push( 'a' ); |
165 |
f( 2 ); |
166 |
log.push( 'b' ); |
167 |
testTimer.runPending(); |
168 |
f( 3 ); |
169 |
log.push( 'c' ); |
170 |
testTimer.runPending(); |
171 |
log.push( 'd' ); |
172 |
assert.deepEqual( log, [ 1, 'a', 'b', 3, 'c', 'd' ], 'debounce 0 ms immediate' ); |
173 |
testTimer.runPending(); |
174 |
|
175 |
} finally { |
176 |
window.setTimeout = setTimeoutReal; |
177 |
window.clearTimeout = clearTimeoutReal; |
178 |
} |
179 |
} ); |
180 |
|
181 |
QUnit.test( 'throttle', function ( assert ) { |
182 |
var f, |
183 |
log = [], |
184 |
testTimer = new OO.ui.TestTimer(), |
185 |
setTimeoutReal = window.setTimeout, |
186 |
clearTimeoutReal = window.clearTimeout; |
187 |
window.setTimeout = testTimer.setTimeout.bind( testTimer ); |
188 |
window.clearTimeout = testTimer.clearTimeout.bind( testTimer ); |
189 |
try { |
190 |
f = OO.ui.throttle( log.push.bind( log ), 50 ); |
191 |
f( 1 ); // runs |
192 |
testTimer.runPending( 20 ); |
193 |
log.push( 'a' ); |
194 |
f( 2 ); // throttled |
195 |
testTimer.runPending( 30 ); |
196 |
log.push( 'b' ); |
197 |
f( 3 ); // throttled |
198 |
log.push( 'c' ); |
199 |
testTimer.runPending( 30 ); // call happens |
200 |
log.push( 'd' ); |
201 |
f( 4 ); // throttled |
202 |
testTimer.runPending( 20 ); |
203 |
log.push( 'e' ); |
204 |
testTimer.runPending( 20 ); |
205 |
log.push( 'f' ); |
206 |
testTimer.runPending( 20 ); // call happens |
207 |
log.push( 'g' ); |
208 |
testTimer.runPending( 20 ); |
209 |
assert.deepEqual( log, [ 1, 'a', 'b', 'c', 3, 'd', 'e', 'f', 4, 'g' ], 'throttle 50 ms' ); |
210 |
|
211 |
log = []; |
212 |
f = OO.ui.throttle( log.push.bind( log ), 0 ); |
213 |
f( 1 ); |
214 |
log.push( 'a' ); |
215 |
f( 2 ); |
216 |
log.push( 'b' ); |
217 |
testTimer.runPending(); |
218 |
f( 3 ); |
219 |
log.push( 'c' ); |
220 |
testTimer.runPending(); |
221 |
log.push( 'd' ); |
222 |
assert.deepEqual( log, [ 1, 'a', 2, 'b', 3, 'c', 'd' ], 'throttle 0 ms' ); |
223 |
testTimer.runPending(); |
224 |
|
225 |
} finally { |
226 |
window.setTimeout = setTimeoutReal; |
227 |
window.clearTimeout = clearTimeoutReal; |
228 |
} |
229 |
} ); |
230 |
|
|
|
/tests/Element.test.js
|
2 problems (2 errors, 0 warnings)
|
Line |
Source |
1 |
QUnit.module( 'Element', { |
2 |
beforeEach: function () { |
3 |
this.fixture = document.createElement( 'div' ); |
4 |
document.body.appendChild( this.fixture ); |
5 |
|
6 |
this.makeFrame = function () { |
7 |
var frame = document.createElement( 'iframe' ); |
8 |
this.fixture.appendChild( frame ); |
9 |
return ( frame.contentWindow && frame.contentWindow.document ) || frame.contentDocument; |
10 |
}; |
11 |
}, |
12 |
afterEach: function () { |
13 |
this.fixture.parentNode.removeChild( this.fixture ); |
14 |
this.fixture = null; |
15 |
} |
16 |
} ); |
17 |
|
18 |
QUnit.test( 'static.infuse', function ( assert ) { |
19 |
var |
Error |
Row 20, Column 25: "Prefer createHTMLDocument to $.parseHTML"
jquery/no-parse-html
|
20 |
$textInputWidget = $( $.parseHTML( '<div aria-disabled=\'false\' id=\'ooui-php-1\' class=\'oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php\' data-ooui=\'{"_":"OO.ui.TextInputWidget"}\'><input type=\'text\' tabindex=\'0\' aria-disabled=\'false\' value=\'\' class=\'oo-ui-inputWidget-input\' /><span class=\'oo-ui-iconElement-icon\'></span><span class=\'oo-ui-indicatorElement-indicator\'></span></div>' ) ), |
Error |
Row 21, Column 20: "Prefer createHTMLDocument to $.parseHTML"
jquery/no-parse-html
|
21 |
$fakeWidget = $( $.parseHTML( '<div data-ooui=\'{"_":"Array"}\'></div>' ) ); |
22 |
assert.ok( OO.ui.Element.static.infuse( $textInputWidget ) ); |
23 |
assert.throws( function () { |
24 |
OO.ui.Element.static.infuse( $fakeWidget ); |
25 |
}, Error ); |
26 |
} ); |
27 |
|
28 |
QUnit.test( 'static.infuse (infusing already infused nodes)', function ( assert ) { |
29 |
var a, b, $node, fixture = this.fixture; |
30 |
|
31 |
function reset() { |
32 |
// ( new OOUI\FieldLayout( new OOUI\ButtonWidget( [ 'id' => 'button' ] ), [ 'id' => 'field' ] ) )->setInfusable( true ) |
33 |
var html = '<div id=\'field\' class=\'oo-ui-layout oo-ui-fieldLayout oo-ui-fieldLayout-align-left\' data-ooui=\'{"_":"OO.ui.FieldLayout","fieldWidget":{"tag":"button"},"align":"left","errors":[],"notices":[],"$overlay":true}\'><div class=\'oo-ui-fieldLayout-body\'><span class=\'oo-ui-fieldLayout-header\'><label class=\'oo-ui-labelElement-label\'></label></span><span class=\'oo-ui-fieldLayout-field\'><span id=\'button\' aria-disabled=\'false\' class=\'oo-ui-widget oo-ui-widget-enabled oo-ui-buttonElement oo-ui-buttonElement-framed oo-ui-buttonWidget\' data-ooui=\'{"_":"OO.ui.ButtonWidget"}\'><a role=\'button\' tabindex=\'0\' aria-disabled=\'false\' rel=\'nofollow\' class=\'oo-ui-buttonElement-button\'><span class=\'oo-ui-iconElement-icon\'></span><span class=\'oo-ui-labelElement-label\'></span><span class=\'oo-ui-indicatorElement-indicator\'></span></a></span></span></div></div>'; |
34 |
$( fixture ).empty().append( html ); |
35 |
} |
36 |
|
37 |
// Infuse a widget, then the same widget (by id) |
38 |
reset(); |
39 |
a = OO.ui.infuse( 'button' ); |
40 |
b = OO.ui.infuse( 'button' ); |
41 |
assert.ok( a instanceof OO.ui.ButtonWidget, 'infuse() returned a ButtonWidget' ); |
42 |
assert.ok( b instanceof OO.ui.ButtonWidget, 'infuse() returned a ButtonWidget' ); |
43 |
assert.strictEqual( a, b, 'Both infuse() calls returned the same widget instance' ); |
44 |
|
45 |
// Infuse a widget, then the same widget (by node) |
46 |
reset(); |
47 |
$node = $( '#button' ); |
48 |
a = OO.ui.infuse( 'button' ); |
49 |
b = OO.ui.infuse( $node ); |
50 |
assert.ok( a instanceof OO.ui.ButtonWidget, 'infuse() returned a ButtonWidget' ); |
51 |
assert.ok( b instanceof OO.ui.ButtonWidget, 'infuse() returned a ButtonWidget' ); |
52 |
assert.strictEqual( a, b, 'Both infuse() calls returned the same widget instance' ); |
53 |
|
54 |
// Infuse a field, then its widget (by id) |
55 |
reset(); |
56 |
a = OO.ui.infuse( 'field' ); |
57 |
b = OO.ui.infuse( 'button' ); |
58 |
assert.ok( a instanceof OO.ui.FieldLayout, 'infuse() returned a FieldLayout' ); |
59 |
assert.ok( b instanceof OO.ui.ButtonWidget, 'infuse() returned a ButtonWidget' ); |
60 |
assert.strictEqual( a.fieldWidget, b, 'Both infuse() calls returned the same widget instance' ); |
61 |
|
62 |
// Infuse a field, then its widget (by node) |
63 |
reset(); |
64 |
$node = $( '#button' ); |
65 |
a = OO.ui.infuse( 'field' ); |
66 |
b = OO.ui.infuse( $node ); |
67 |
assert.ok( a instanceof OO.ui.FieldLayout, 'infuse() returned a FieldLayout' ); |
68 |
assert.ok( b instanceof OO.ui.ButtonWidget, 'infuse() returned a ButtonWidget' ); |
69 |
assert.strictEqual( a.fieldWidget, b, 'Both infuse() calls returned the same widget instance' ); |
70 |
|
71 |
// Infuse a widget, then its field (by id) |
72 |
reset(); |
73 |
a = OO.ui.infuse( 'button' ); |
74 |
b = OO.ui.infuse( 'field' ); |
75 |
assert.ok( a instanceof OO.ui.ButtonWidget, 'infuse() returned a ButtonWidget' ); |
76 |
assert.ok( b instanceof OO.ui.FieldLayout, 'infuse() returned a FieldLayout' ); |
77 |
assert.strictEqual( b.fieldWidget, a, 'Both infuse() calls returned the same widget instance' ); |
78 |
|
79 |
// Infuse a widget, then its field (by node) |
80 |
reset(); |
81 |
$node = $( '#field' ); |
82 |
a = OO.ui.infuse( 'button' ); |
83 |
b = OO.ui.infuse( $node ); |
84 |
assert.ok( a instanceof OO.ui.ButtonWidget, 'infuse() returned a ButtonWidget' ); |
85 |
assert.ok( b instanceof OO.ui.FieldLayout, 'infuse() returned a FieldLayout' ); |
86 |
assert.strictEqual( b.fieldWidget, a, 'Both infuse() calls returned the same widget instance' ); |
87 |
|
88 |
// Infuse a widget with extra config |
89 |
reset(); |
90 |
a = OO.ui.infuse( $( '#button' ), { flags: [ 'extra' ] } ); |
91 |
assert.deepEqual( a.getFlags(), [ 'extra' ], 'infuse with extra config' ); |
92 |
} ); |
93 |
|
94 |
QUnit.test( 'static.getDocument', function ( assert ) { |
95 |
var frameDoc, frameEl, frameDiv, |
96 |
el = this.fixture, |
97 |
div = document.createElement( 'div' ), |
98 |
$el = $( this.fixture ), |
99 |
$div = $( '<div>' ), |
100 |
win = window, |
101 |
doc = document; |
102 |
|
103 |
frameDoc = this.makeFrame(); |
104 |
frameEl = frameDoc.createElement( 'span' ); |
105 |
frameDoc.documentElement.appendChild( frameEl ); |
106 |
frameDiv = frameDoc.createElement( 'div' ); |
107 |
|
108 |
assert.strictEqual( OO.ui.Element.static.getDocument( $el ), doc, 'jQuery' ); |
109 |
assert.strictEqual( OO.ui.Element.static.getDocument( $div ), doc, 'jQuery (detached)' ); |
110 |
assert.strictEqual( OO.ui.Element.static.getDocument( el ), doc, 'HTMLElement' ); |
111 |
assert.strictEqual( OO.ui.Element.static.getDocument( div ), doc, 'HTMLElement (detached)' ); |
112 |
assert.strictEqual( OO.ui.Element.static.getDocument( win ), doc, 'Window' ); |
113 |
assert.strictEqual( OO.ui.Element.static.getDocument( doc ), doc, 'HTMLDocument' ); |
114 |
|
115 |
assert.strictEqual( OO.ui.Element.static.getDocument( frameEl ), frameDoc, 'HTMLElement (framed)' ); |
116 |
assert.strictEqual( OO.ui.Element.static.getDocument( frameDiv ), frameDoc, 'HTMLElement (framed, detached)' ); |
117 |
assert.strictEqual( OO.ui.Element.static.getDocument( frameDoc ), frameDoc, 'HTMLDocument (framed)' ); |
118 |
|
119 |
assert.strictEqual( OO.ui.Element.static.getDocument( {} ), null, 'Invalid' ); |
120 |
} ); |
121 |
|
122 |
QUnit.test( 'getElementDocument', function ( assert ) { |
123 |
var el, doc; |
124 |
|
125 |
doc = document; |
126 |
el = new OO.ui.Element(); |
127 |
assert.strictEqual( el.getElementDocument(), doc ); |
128 |
} ); |
129 |
|
|
|
/tests/JSPHP.test.karma.js
|
1 problem (1 error, 0 warnings)
|
Severity |
Rule |
Error |
Row 34, Column 19: "Prefer createHTMLDocument to $.parseHTML"
jquery/no-parse-html
|
Line |
Source |
1 |
QUnit.module( 'JSPHP' ); |
2 |
|
3 |
( function () { |
4 |
// Generate some tests based on the test suite data and HTML from PHP version. |
5 |
var theme, klassName, |
6 |
themes = { |
7 |
ApexTheme: new OO.ui.ApexTheme(), |
8 |
WikimediaUITheme: new OO.ui.WikimediaUITheme() |
9 |
}; |
10 |
|
11 |
function unstub( value ) { |
12 |
var config; |
13 |
if ( typeof value === 'string' && value.substr( 0, 13 ) === '_placeholder_' ) { |
14 |
value = JSON.parse( value.substr( 13 ) ); |
15 |
config = OO.copy( value.config, null, unstub ); |
16 |
return new OO.ui[ value.class ]( config ); |
17 |
} |
18 |
} |
19 |
|
20 |
function makeTest( theme, klassName, infuseOnly, tests, output ) { |
21 |
// eslint-disable-next-line qunit/require-expect |
22 |
QUnit.test( theme + ': ' + klassName, function ( assert ) { |
23 |
var test, config, instance, infused, $fromPhp, i, testName; |
24 |
|
25 |
assert.expect( tests.length * ( infuseOnly ? 1 : 2 ) ); |
26 |
|
27 |
OO.ui.theme = themes[ theme ]; |
28 |
for ( i = 0; i < tests.length; i++ ) { |
29 |
test = tests[ i ]; |
30 |
// Unstub placeholders |
31 |
config = OO.copy( test.config, null, unstub ); |
32 |
|
33 |
instance = new OO.ui[ test.class ]( config ); |
Error |
Row 34, Column 19: "Prefer createHTMLDocument to $.parseHTML"
jquery/no-parse-html
|
34 |
$fromPhp = $( $.parseHTML( output[ i ] ) ); |
35 |
|
36 |
$( 'body' ).append( instance.$element, $fromPhp ); |
37 |
|
38 |
testName = JSON.stringify( test.config ); |
39 |
if ( !infuseOnly ) { |
40 |
assert.equalDomElement( instance.$element[ 0 ], $fromPhp[ 0 ], testName ); |
41 |
} |
42 |
|
43 |
infused = OO.ui.infuse( $fromPhp[ 0 ] ); |
44 |
|
45 |
assert.equalDomElement( instance.$element[ 0 ], infused.$element[ 0 ], testName + ' (infuse)' ); |
46 |
instance.$element.add( infused.$element ).detach(); |
47 |
} |
48 |
} ); |
49 |
} |
50 |
|
51 |
// Updating theme classes is normally debounced, but we need to do it immediately |
52 |
// if we want the tests to be synchronous |
53 |
OO.ui.Theme.prototype.queueUpdateElementClasses = OO.ui.Theme.prototype.updateElementClasses; |
54 |
|
55 |
// Various things end up in the default overlay when infusing, but that's annoying |
56 |
// because we need to compare them, so let's prevent that |
57 |
OO.ui.getDefaultOverlay = function () { |
58 |
return null; |
59 |
}; |
60 |
|
61 |
/* global testSuiteConfigs, testSuitePHPOutput */ |
62 |
for ( klassName in testSuiteConfigs ) { |
63 |
for ( theme in themes ) { |
64 |
makeTest( |
65 |
theme, |
66 |
klassName, |
67 |
testSuiteConfigs[ klassName ].infuseonly, |
68 |
testSuiteConfigs[ klassName ].tests, |
69 |
testSuitePHPOutput[ theme ][ klassName ] |
70 |
); |
71 |
} |
72 |
} |
73 |
|
74 |
}() ); |
75 |
|
|
|
/tests/JSPHP.test.standalone.js
|
0 problems
|
|
/tests/mixins/FlaggedElement.test.js
|
0 problems
|
|
/tests/mixins/LabelElement.test.js
|
0 problems
|
|
/tests/Process.test.js
|
6 problems (6 errors, 0 warnings)
|
Line |
Source |
1 |
QUnit.module( 'OO.ui.Process' ); |
2 |
|
3 |
/* Tests */ |
4 |
|
5 |
QUnit.test( 'next', function ( assert ) { |
6 |
var process = new OO.ui.Process(), |
7 |
result = []; |
8 |
|
9 |
return process |
10 |
.next( function () { |
11 |
result.push( 0 ); |
12 |
} ) |
13 |
.next( function () { |
14 |
result.push( 1 ); |
15 |
} ) |
16 |
.next( function () { |
17 |
result.push( 2 ); |
18 |
} ) |
19 |
.execute() |
20 |
.then( function () { |
21 |
assert.deepEqual( result, [ 0, 1, 2 ], 'Steps can be added at the end' ); |
22 |
} ); |
23 |
} ); |
24 |
|
25 |
QUnit.test( 'first', function ( assert ) { |
26 |
var process = new OO.ui.Process(), |
27 |
result = []; |
28 |
|
29 |
return process |
30 |
.first( function () { |
31 |
result.push( 0 ); |
32 |
} ) |
33 |
.first( function () { |
34 |
result.push( 1 ); |
35 |
} ) |
36 |
.first( function () { |
37 |
result.push( 2 ); |
38 |
} ) |
39 |
.execute() |
40 |
.then( function () { |
41 |
assert.deepEqual( result, [ 2, 1, 0 ], 'Steps can be added at the beginning' ); |
42 |
} ); |
43 |
} ); |
44 |
|
45 |
QUnit.test( 'execute (async)', function ( assert ) { |
46 |
var process = new OO.ui.Process(), |
47 |
result = []; |
48 |
|
49 |
return process |
50 |
.next( function () { |
Error |
Row 51, Column 19: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
51 |
var deferred = $.Deferred(); |
52 |
|
53 |
setTimeout( function () { |
54 |
result.push( 1 ); |
55 |
deferred.resolve(); |
56 |
}, 10 ); |
57 |
|
58 |
return deferred.promise(); |
59 |
} ) |
60 |
.first( function () { |
Error |
Row 61, Column 19: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
61 |
var deferred = $.Deferred(); |
62 |
|
63 |
setTimeout( function () { |
64 |
result.push( 0 ); |
65 |
deferred.resolve(); |
66 |
}, 10 ); |
67 |
|
68 |
return deferred.promise(); |
69 |
} ) |
70 |
.next( function () { |
71 |
result.push( 2 ); |
72 |
} ) |
73 |
.execute() |
74 |
.then( function () { |
75 |
assert.deepEqual( |
76 |
result, |
77 |
[ 0, 1, 2 ], |
78 |
'Synchronous and asynchronous steps are executed in the correct order' |
79 |
); |
80 |
} ); |
81 |
} ); |
82 |
|
83 |
QUnit.test( 'execute (return false)', function ( assert ) { |
84 |
var process = new OO.ui.Process(), |
85 |
result = []; |
86 |
|
87 |
return process |
88 |
.next( function () { |
Error |
Row 89, Column 19: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
89 |
var deferred = $.Deferred(); |
90 |
|
91 |
setTimeout( function () { |
92 |
result.push( 0 ); |
93 |
deferred.resolve(); |
94 |
}, 10 ); |
95 |
|
96 |
return deferred.promise(); |
97 |
} ) |
98 |
.next( function () { |
99 |
result.push( 1 ); |
100 |
return false; |
101 |
} ) |
102 |
.next( function () { |
103 |
// Should never be run because previous step is rejected |
104 |
result.push( 2 ); |
105 |
} ) |
106 |
.execute() |
107 |
.then( null, function () { |
108 |
assert.deepEqual( |
109 |
result, |
110 |
[ 0, 1 ], |
111 |
'Process is stopped when a step returns false' |
112 |
); |
Error |
Row 113, Column 11: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
113 |
return $.Deferred().resolve(); |
114 |
} ); |
115 |
} ); |
116 |
|
117 |
QUnit.test( 'execute (async reject)', function ( assert ) { |
118 |
var process = new OO.ui.Process(), |
119 |
result = []; |
120 |
|
121 |
return process |
122 |
.next( function () { |
123 |
result.push( 0 ); |
124 |
} ) |
125 |
.next( function () { |
Error |
Row 126, Column 19: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
126 |
var deferred = $.Deferred(); |
127 |
|
128 |
setTimeout( function () { |
129 |
result.push( 1 ); |
130 |
deferred.reject(); |
131 |
}, 10 ); |
132 |
|
133 |
return deferred.promise(); |
134 |
} ) |
135 |
.next( function () { |
136 |
// Should never be run because previous step is rejected |
137 |
result.push( 2 ); |
138 |
} ) |
139 |
.execute() |
140 |
.then( null, function () { |
141 |
assert.deepEqual( |
142 |
result, |
143 |
[ 0, 1 ], |
144 |
'Process is stopped when a step returns a promise that is then rejected' |
145 |
); |
Error |
Row 146, Column 11: "Prefer Promise to $.Deferred"
jquery/no-deferred
|
146 |
return $.Deferred().resolve(); |
147 |
} ); |
148 |
} ); |
149 |
|
150 |
QUnit.test( 'execute (wait)', function ( assert ) { |
151 |
var process = new OO.ui.Process(), |
152 |
result = []; |
153 |
|
154 |
process |
155 |
.next( function () { |
156 |
result.push( 'A' ); |
157 |
return 10; |
158 |
} ) |
159 |
.next( function () { |
160 |
result.push( 'B' ); |
161 |
} ); |
162 |
|
163 |
// Steps defined above don't run until execute() |
164 |
result.push( 'before' ); |
165 |
|
166 |
// Process yields between step A and B |
167 |
setTimeout( function () { |
168 |
result.push( 'yield' ); |
169 |
} ); |
170 |
|
171 |
return process |
172 |
.execute() |
173 |
.then( function () { |
174 |
assert.deepEqual( |
175 |
result, |
176 |
[ 'before', 'A', 'yield', 'B' ], |
177 |
'Process is stopped when a step returns a promise that is then rejected' |
178 |
); |
179 |
} ); |
180 |
} ); |
181 |
|
|
|
/tests/QUnit.assert.equalDomElement.js
|
0 problems
|
|
/tests/TestTimer.js
|
0 problems
|
|
/tests/widgets/NumberInputWidget.test.js
|
0 problems
|
|
/tests/widgets/SelectWidget.test.js
|
0 problems
|
|
/tests/widgets/TagMultiselectWidget.test.js
|
0 problems
|
|
/tests/windows.test.js
|
0 problems
|