Merge lp:~bac/juju-gui/autosize-plugin into lp:juju-gui/experimental

Proposed by Brad Crittenden on 2013-04-30
Status: Needs review
Proposed branch: lp:~bac/juju-gui/autosize-plugin
Merge into: lp:juju-gui/experimental
Diff against target: 339 lines (+289/-0)
5 files modified
Makefile (+2/-0)
app/modules-debug.js (+5/-0)
app/plugins/textarea-autosize.js (+191/-0)
test/index.html (+1/-0)
test/test_textarea_autosize.js (+90/-0)
To merge this branch: bzr merge lp:~bac/juju-gui/autosize-plugin
Reviewer Review Type Date Requested Status
Juju GUI Hackers 2013-04-30 Pending
Review via email: mp+161685@code.launchpad.net

Description of the change

Add textarea-autosize plugin.

https://codereview.appspot.com/9024045/

To post a comment you must log in.
Brad Crittenden (bac) wrote :

Reviewers: mp+161685_code.launchpad.net,

Message:
Please take a look.

Description:
Add textarea-autosize plugin.

https://code.launchpad.net/~bac/juju-gui/autosize-plugin/+merge/161685

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/9024045/

Affected files:
   M Makefile
   A [revision details]
   M app/modules-debug.js
   A app/plugins/textarea-autosize.js
   M test/index.html
   A test/test_textarea_autosize.js

Madison Scott-Clary (makyo) wrote :

Code LGTM, didn't QA yet, but can if needed

https://codereview.appspot.com/9024045/diff/1/app/plugins/textarea-autosize.js
File app/plugins/textarea-autosize.js (right):

https://codereview.appspot.com/9024045/diff/1/app/plugins/textarea-autosize.js#newcode46
app/plugins/textarea-autosize.js:46: //textarea.on('input',
this._autoSizeHandler, this);
Remove or add comment as to why this is commented out.

https://codereview.appspot.com/9024045/diff/1/app/plugins/textarea-autosize.js#newcode141
app/plugins/textarea-autosize.js:141: // this.mirrorElement.value =
textarea.value + options.append;
Remove or explain.

https://codereview.appspot.com/9024045/diff/1/app/plugins/textarea-autosize.js#newcode173
app/plugins/textarea-autosize.js:173: // XXX: The original had a very
short timeout to avoid IE wetting
This seems like a safe enough flag to have around, but will defer to
others.

https://codereview.appspot.com/9024045/

Unmerged revisions

633. By Brad Crittenden on 2013-04-30

lint

632. By Brad Crittenden on 2013-04-30

Remove describe.only

631. By Brad Crittenden on 2013-04-30

More tests

630. By Brad Crittenden on 2013-04-30

wire up autosize tests

629. By Brad Crittenden on 2013-04-30

Added NS to plugin

628. By Brad Crittenden on 2013-04-30

Add app/plugins

627. By Brad Crittenden on 2013-04-30

Added textarea autosize plugin

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2013-04-30 14:56:08 +0000
3+++ Makefile 2013-04-30 19:27:26 +0000
4@@ -320,6 +320,7 @@
5 build-debug/juju-ui/subapps \
6 build-debug/juju-ui/views \
7 build-debug/juju-ui/widgets \
8+ build-debug/juju-ui/plugins \
9 build-debug/juju-ui/assets/javascripts \
10 build-debug/juju-ui/templates.js
11
12@@ -368,6 +369,7 @@
13 ln -sf "$(PWD)/app/subapps" build-debug/juju-ui/
14 ln -sf "$(PWD)/app/views" build-debug/juju-ui/
15 ln -sf "$(PWD)/app/widgets" build-debug/juju-ui/
16+ ln -sf "$(PWD)/app/plugins" build-debug/juju-ui/
17 ln -sf "$(PWD)/app/assets/javascripts/yui/yui/yui-debug.js" \
18 build-debug/juju-ui/assets/all-yui.js
19 ln -sf "$(PWD)/build-shared/juju-ui/templates.js" build-debug/juju-ui/
20
21=== modified file 'app/modules-debug.js'
22--- app/modules-debug.js 2013-04-30 15:56:54 +0000
23+++ app/modules-debug.js 2013-04-30 19:27:26 +0000
24@@ -114,6 +114,11 @@
25 fullpath: '/juju-ui/assets/javascripts/sub-app.js'
26 },
27
28+ // Plugins
29+ 'textarea-autosize': {
30+ fullpath: '/juju-ui/plugins/textarea-autosize.js'
31+ },
32+
33 // Views
34 'juju-landscape': {
35 fullpath: '/juju-ui/views/landscape.js'
36
37=== added directory 'app/plugins'
38=== added file 'app/plugins/textarea-autosize.js'
39--- app/plugins/textarea-autosize.js 1970-01-01 00:00:00 +0000
40+++ app/plugins/textarea-autosize.js 2013-04-30 19:27:26 +0000
41@@ -0,0 +1,191 @@
42+'use strict';
43+
44+/**
45+ A plugin for textareas that causes them to automatically resize when users
46+ enter additional text. The textarea will expand vertically without adding
47+ scrollbars. An enhancement would be to specify a maximum height after which
48+ scrollbars are added.
49+
50+ Usage: Y.all(textareas).plug(Y.Autosize)
51+ */
52+
53+YUI.add('textarea-autosize', function(Y) {
54+
55+ var ns = Y.namespace('juju.plugins');
56+
57+ ns.TextareaAutosize = Y.Base.create('textarea-autosize', Y.Plugin.Base, [], {
58+
59+ active: false,
60+ boxOffset: 0,
61+ minHeight: undefined,
62+ mirrorElement: Y.Node.create(
63+ '<textarea data-autosize="true" tabindex="-1" ' +
64+ 'style="position:absolute; top:-999px; left:0; ' +
65+ 'right:auto; bottom:auto; border:0; -moz-box-sizing:content-box; ' +
66+ '-webkit-box-sizing:content-box; box-sizing:content-box; ' +
67+ 'word-wrap:break-word; height:0 !important; ' +
68+ 'min-height:0 !important; overflow:hidden;"/>'),
69+
70+ stylesToMirror: [
71+ 'fontFamily',
72+ 'fontSize',
73+ 'fontWeight',
74+ 'fontStyle',
75+ 'letterSpacing',
76+ 'textTransform',
77+ 'wordSpacing',
78+ 'textIndent',
79+ 'lineHeight'
80+ ],
81+
82+ /**
83+ @method initializer
84+ */
85+ initializer: function() {
86+ var textarea = this.get('host');
87+ //textarea.on('input', this._autoSizeHandler, this);
88+ textarea.on('keydown', this._autoSizeHandler, this);
89+ textarea.on('valuechange', this._autoSizeHandler, this);
90+ this.minHeight = this._getMinHeight();
91+ this._initMirror();
92+ },
93+
94+ /**
95+ Get the minimum height for the textarea.
96+ @method _getMinHeight
97+ @private
98+ @return {Int} the min height.
99+ */
100+ _getMinHeight: function() {
101+ var borderBox = 'border-box',
102+ textarea = this.get('host'),
103+ // XXX bac: is clientHeight what we want here?
104+ height = textarea.get('clientHeight');
105+
106+ if (textarea.getStyle('box-sizing') === borderBox ||
107+ textarea.getStyle('-moz-box-sizing') === borderBox ||
108+ textarea.getStyle('-webkit-box-sizing') === borderBox) {
109+ // XXX: scrollHeight is the same as outerHeight?
110+ this.boxOffset = textarea.get('scrollHeight') - height;
111+ }
112+
113+ return Math.max(
114+ this._toInt(textarea.getStyle('minHeight')) - this.boxOffset,
115+ height);
116+ },
117+
118+ /**
119+ Handle input events for the textarea and perform the resizing if
120+ required.
121+
122+ @method _autoSizeHandler
123+ @private
124+ */
125+ _autoSizeHandler: function(evt) {
126+ this._adjust();
127+ },
128+
129+ /**
130+ Initialize the invisible mirrorElement.
131+
132+ @method _initMirror
133+ @private
134+ */
135+ _initMirror: function() {
136+ var textarea = this.get('host');
137+ if (!this.mirrorElement.ancestor('body')) {
138+ Y.one('body').append(this.mirrorElement);
139+ this.mirrorElement.set('value', '\n\n\n');
140+ this.mirrorElement.set('scrollTop', 9e4);
141+ }
142+
143+ this.mirrorElement.set('className', textarea.get('className'));
144+ Y.Array.each(this.stylesToMirror, function(v) {
145+ this.mirrorElement.setStyle(v, textarea.getStyle(v));
146+ }, this);
147+ },
148+
149+ /**
150+ Helper to convert strings to a base 10 int.
151+
152+ @method _toInt
153+ @private
154+ @param {String} v String to be parsed.
155+ @return {Int} integer value or 'NaN'.
156+ */
157+ _toInt: function(s) {
158+ return parseInt(s, 10);
159+ },
160+
161+ /**
162+ Adjust the textarea based on user input. This method is called on every
163+ keystroke so it needs to be speedy.
164+
165+ XXX bac: the original JQuery version mentioned trying to use bare
166+ Javascript. The conversion uses lots of YUI because it was the most
167+ straightforward. Does this need to be reverted to bare JS?
168+
169+ @method _adjust
170+ @private
171+ */
172+ _adjust: function() {
173+ var height,
174+ overflow,
175+ original;
176+
177+ // the active flag keeps IE from tripping all over itself. Otherwise
178+ // actions in the adjust function will cause IE to call adjust again.
179+ if (!this.active) {
180+ var textarea = this.get('host');
181+ this.active = true;
182+ // this.mirrorElement.value = textarea.value + options.append;
183+ this.mirrorElement.set('value', textarea.get('value'));
184+ this.mirrorElement.setStyle('overflowY',
185+ textarea.getStyle('overflowY'));
186+ original = this._toInt(textarea.getStyle('height'));
187+
188+ // Update the width in case the original textarea width has changed
189+ // A floor of 0 is needed because IE8 returns a negative
190+ // value for hidden textareas, raising an error.
191+ this.mirrorElement.setStyle('width',
192+ Math.max(textarea.getStyle('width'), 0));
193+
194+ // Get the height of the mirror.
195+ height = this._toInt(this.mirrorElement.get('scrollHeight'));
196+ var maxHeight = this._toInt(textarea.getStyle('maxHeight'));
197+ // Opera returns '-1px' when max-height is set to 'none'.
198+ maxHeight = maxHeight && maxHeight > 0 ? maxHeight : 9e4;
199+ if (height > maxHeight) {
200+ height = maxHeight;
201+ overflow = 'scroll';
202+ } else if (height < this.minHeight) {
203+ height = this.minHeight;
204+ }
205+ height += this.boxOffset;
206+ textarea.setStyle('overflowY', overflow || 'hidden');
207+
208+ if (original !== height) {
209+ // XXX: all of this code assumes sizes are in px in the
210+ // CSS. Will break if specified in other units.
211+ textarea.setStyle('height', height + 'px');
212+ }
213+ }
214+ // XXX: The original had a very short timeout to avoid IE wetting
215+ // itself. Unsure if it is still needed.
216+ this.active = false;
217+ }
218+ },{
219+
220+ NS: 'TextareaAutosize'
221+ }
222+ );
223+},
224+
225+'0.1.0', {
226+ requires: [
227+ 'node',
228+ 'event',
229+ 'base-build',
230+ 'plugin'
231+ ]
232+});
233
234=== modified file 'test/index.html'
235--- test/index.html 2013-04-25 20:03:36 +0000
236+++ test/index.html 2013-04-30 19:27:26 +0000
237@@ -75,6 +75,7 @@
238 <script src="test_sub_app.js"></script>
239 <script src="test_tabview.js"></script>
240 <script src="test_templates.js"></script>
241+ <script src="test_textarea_autosize.js"></script>
242 <script src="test_topology.js"></script>
243 <script src="test_topology_relation.js"></script>
244 <script src="test_unit_view.js"></script>
245
246=== added file 'test/test_textarea_autosize.js'
247--- test/test_textarea_autosize.js 1970-01-01 00:00:00 +0000
248+++ test/test_textarea_autosize.js 2013-04-30 19:27:26 +0000
249@@ -0,0 +1,90 @@
250+'use strict';
251+
252+describe('textarea autosize plugin', function() {
253+ var Y, container, textarea;
254+
255+ before(function(done) {
256+ Y = YUI(GlobalConfig).use([
257+ 'textarea-autosize',
258+ 'node-event-simulate'],
259+ function(Y) {
260+ done();
261+ });
262+ });
263+
264+ beforeEach(function() {
265+ container = Y.Node.create('<div id="container"></div>');
266+ textarea = Y.Node.create('<textarea class="autosize"></textarea>');
267+ container.append(textarea);
268+ Y.one(document.body).prepend(container);
269+ });
270+
271+ afterEach(function() {
272+ textarea.remove().destroy(true);
273+ container.remove().destroy(true);
274+ });
275+
276+ var setAndTrigger = function(textarea, v) {
277+ textarea.set('value', v);
278+ textarea.focus();
279+ textarea.simulate('keydown', {keyCode: 83});
280+ };
281+
282+ it('plugs into a textarea', function() {
283+ var node = Y.one('textarea.autosize');
284+ node.plug(Y.juju.plugins.TextareaAutosize);
285+ assert.isDefined(node.TextareaAutosize);
286+ });
287+
288+ it('plugs into lots of textareas', function() {
289+ var textarea2 = Y.Node.create('<textarea class="autosize"></textarea>');
290+ var textarea3 = Y.Node.create('<textarea></textarea>');
291+ container.append(textarea2);
292+ container.append(textarea3);
293+ var nodes = Y.all('textarea');
294+ nodes.plug(Y.juju.plugins.TextareaAutosize);
295+ assert.isDefined(textarea.TextareaAutosize);
296+ assert.isDefined(textarea2.TextareaAutosize);
297+ assert.isDefined(textarea3.TextareaAutosize);
298+ });
299+
300+ it('plugs into lots of textareas, but selectively', function() {
301+ var textarea2 = Y.Node.create('<textarea class="autosize"></textarea>');
302+ var textarea3 = Y.Node.create('<textarea></textarea>');
303+ container.append(textarea2);
304+ container.append(textarea3);
305+ // Textareas with autosize class.
306+ var nodes = Y.all('textarea.autosize');
307+ nodes.plug(Y.juju.plugins.TextareaAutosize);
308+ assert.isDefined(textarea.TextareaAutosize);
309+ assert.isDefined(textarea2.TextareaAutosize);
310+ assert.isUndefined(textarea3.TextareaAutosize);
311+ });
312+
313+ it('calculates the minHeight', function() {
314+ var node = Y.one('textarea.autosize');
315+ node.plug(Y.juju.plugins.TextareaAutosize);
316+ var original = textarea.TextareaAutosize._getMinHeight();
317+ setAndTrigger(textarea, 'how\nnow\nbrown\ncow\nand\nstuff');
318+ var bigger = textarea.TextareaAutosize._getMinHeight();
319+ // The size should have grown.
320+ assert.isTrue(bigger > original);
321+ setAndTrigger(textarea, 'one line');
322+ var smaller = textarea.TextareaAutosize._getMinHeight();
323+ assert.isTrue(smaller < bigger);
324+ });
325+
326+ it('sets the height on the textarea', function() {
327+ var node = Y.one('textarea.autosize');
328+ node.plug(Y.juju.plugins.TextareaAutosize);
329+ var original = parseInt(textarea.getStyle('height'), 10);
330+ setAndTrigger(textarea, 'how\nnow\nbrown\ncow\nand\nstuff');
331+ var bigger = parseInt(textarea.getStyle('height'), 10);
332+ // The height should have grown...
333+ assert.isTrue(bigger > original);
334+ setAndTrigger(textarea, 'one line');
335+ var smaller = parseInt(textarea.getStyle('height'), 10);
336+ // ..and then shrunk.
337+ assert.isTrue(smaller < bigger);
338+ });
339+});

Subscribers

People subscribed via source and target branches