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

Proposed by Brad Crittenden
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 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.
Revision history for this message
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

Revision history for this message
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

lint

632. By Brad Crittenden

Remove describe.only

631. By Brad Crittenden

More tests

630. By Brad Crittenden

wire up autosize tests

629. By Brad Crittenden

Added NS to plugin

628. By Brad Crittenden

Add app/plugins

627. By Brad Crittenden

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