Skip to content
This repository has been archived by the owner on Nov 22, 2021. It is now read-only.

Commit

Permalink
fix(tagsInput): Change input width accordingly to its content
Browse files Browse the repository at this point in the history
Create a new directive to dynamically change the input width so its
content is always visible. For that directive to work, most CSS type
selectors must be changed into class selectors so it's possible to
easily "borrow" the input style and calculate the width of its content.

BREAKING CHANGE: Since CSS selectors must be changed, custom stylesheets
based on the old selectors will conflict with this fix. They'll have to be
updated to use class selectors.

Closes #6.
  • Loading branch information
mbenford committed Dec 24, 2013
1 parent 5a58a92 commit 8abdf79
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 30 deletions.
9 changes: 8 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ module.exports = function(grunt) {
// Sets all files used by the script
files: {
js: {
src: ['src/keycodes.js', 'src/tags-input.js', 'src/auto-complete.js', 'src/transclude-append.js', 'src/configuration.js'],
src: [
'src/keycodes.js',
'src/tags-input.js',
'src/auto-complete.js',
'src/transclude-append.js',
'src/autosize.js',
'src/configuration.js'
],
out: 'build/<%= pkg.name %>.js',
outMin: 'tmp/<%= pkg.name %>.min.js'
},
Expand Down
8 changes: 4 additions & 4 deletions css/autocomplete.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
z-index: 999;
}

.ngTagsInput .autocomplete ul {
.ngTagsInput .autocomplete .suggestion-list {
margin: 0;
padding: 0;
list-style-type: none;
}

.ngTagsInput .autocomplete li {
.ngTagsInput .autocomplete .suggestion-item {
padding: 3px 16px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
Expand All @@ -29,12 +29,12 @@
text-overflow: ellipsis;
}

.ngTagsInput .autocomplete li.selected {
.ngTagsInput .autocomplete .suggestion-item.selected {
color: #fff;
background-color: #0097cf
}

.ngTagsInput .autocomplete li em {
.ngTagsInput .autocomplete .suggestion-item em {
font-weight: bold;
font-style: normal;
}
16 changes: 8 additions & 8 deletions css/tags-input.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
overflow-x: hidden;
word-wrap: break-word;
font-size: 14px;
cursor: text;
}

.ngTagsInput .tags.focused {
Expand All @@ -33,14 +34,14 @@
box-shadow: 0px 0px 3px 1px rgba(5,139,242,0.6);
}

.ngTagsInput .tags ul {
.ngTagsInput .tags .tag-list {
margin: 0px;
padding: 0px;
overflow: visible;
list-style-type: none;
}

.ngTagsInput .tags li {
.ngTagsInput .tags .tag-item {
margin: 2px;
padding-left: 4px;
display: inline-block;
Expand All @@ -56,10 +57,9 @@
background: -o-linear-gradient(top, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 47%,rgba(161,219,255,1) 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 47%,rgba(161,219,255,1) 100%); /* IE10+ */
background: linear-gradient(to bottom, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 47%,rgba(161,219,255,1) 100%); /* W3C */

}

.ngTagsInput .tags li.selected {
.ngTagsInput .tags .tag-item.selected {
background: rgb(254,187,187); /* Old browsers */
background: -moz-linear-gradient(top, rgba(254,187,187,1) 0%, rgba(254,144,144,1) 45%, rgba(255,92,92,1) 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(254,187,187,1)), color-stop(45%,rgba(254,144,144,1)), color-stop(100%,rgba(255,92,92,1))); /* Chrome,Safari4+ */
Expand All @@ -69,11 +69,11 @@
background: linear-gradient(to bottom, rgba(254,187,187,1) 0%,rgba(254,144,144,1) 45%,rgba(255,92,92,1) 100%); /* W3C */
}

.ngTagsInput .tags span {
.ngTagsInput .tags .tag-item span {
font: 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.ngTagsInput .tags button {
.ngTagsInput .tags .tag-item button {
margin: 0px;
border: none;
background: none;
Expand All @@ -83,11 +83,11 @@
vertical-align: middle;
}

.ngTagsInput .tags button:active {
.ngTagsInput .tags .tag-item button:active {
color: #ff0000;
}

.ngTagsInput .tags input {
.ngTagsInput .tags .tag-input {
border: 0px;
outline: none;
margin: 2px;
Expand Down
2 changes: 2 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ module.exports = function(config) {
'test/tags-input.spec.js',
'test/auto-complete.spec.js',
'test/transclude-append.spec.js',
'test/autosize.spec.js',
'src/keycodes.js',
'src/tags-input.js',
'src/auto-complete.js',
'src/transclude-append.js',
'src/autosize.js',
'src/configuration.js',
'templates/*.html'
],
Expand Down
46 changes: 46 additions & 0 deletions src/autosize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

/**
* @ngDoc directive
* @name tagsInput.directive:tiAutosize
*
* @description
* Automatically sets the input's width so its content is always visible. Used internally by tagsInput directive.
*/
tagsInput.directive('tiAutosize', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var span, resize;

span = angular.element('<span class="tag-input"></span>');
span.css('display', 'none')
.css('visibility', 'hidden')
.css('width', 'auto');

element.parent().append(span);

resize = function(value) {
var originalValue = value;

if (angular.isString(value) && value.length === 0) {
value = element.attr('placeholder') || '';
}
span.text(value);
span.css('display', '');
try {
element.css('width', span.prop('offsetWidth') + 'px');
}
finally {
span.css('display', 'none');
}

return originalValue;
};

ctrl.$parsers.unshift(resize);
ctrl.$formatters.unshift(resize);
}
};
});
5 changes: 3 additions & 2 deletions templates/auto-complete.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<div class="autocomplete" ng-show="suggestionList.visible">
<ul>
<li ng-repeat="item in suggestionList.items | limitTo:options.maxResultsToShow"
<ul class="suggestion-list">
<li class="suggestion-item"
ng-repeat="item in suggestionList.items | limitTo:options.maxResultsToShow"
ng-class="{selected: item == suggestionList.selected}"
ng-click="addSuggestion()"
ng-mouseenter="suggestionList.select($index)"
Expand Down
12 changes: 6 additions & 6 deletions templates/tags-input.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<div class="ngTagsInput" tabindex="-1" ng-class="options.customClass" transclude-append>
<div class="ngTagsInput" tabindex="-1" ng-class="options.customClass" transclude-append>
<div class="tags" ng-class="{focused: hasFocus}">
<ul>
<li ng-repeat="tag in tags" ng-class="getCssClass($index)">
<ul class="tag-list">
<li class="tag-item" ng-repeat="tag in tags" ng-class="getCssClass($index)">
<span>{{tag}}</span>
<button type="button" ng-click="remove($index)">{{options.removeTagSymbol}}</button>
</li>
</ul>
<input type="text"
<input type="text" class="tag-input"
placeholder="{{options.placeholder}}"
size="{{options.placeholder.length}}"
maxlength="{{options.maxLength}}"
tabindex="{{options.tabindex}}"
ng-model="newTag"
ng-change="newTagChange()">
ng-change="newTagChange()"
ti-autosize>
</div>
</div>
79 changes: 79 additions & 0 deletions test/autosize.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict';

describe('autosize directive', function() {
var $scope, $document, $compile,
element;

// The style tag should be created once, so we can't do it within a beforeEach() callback
$('<style> .tag-input { box-sizing: border-box; border: 1px; padding: 2px; font: Arial 18px; }</style>').appendTo('head');

beforeEach(function() {
module('ngTagsInput');

inject(function($rootScope, _$document_, _$compile_) {
$scope = $rootScope;
$document = _$document_;
$compile = _$compile_;
});
});

function compile() {
var attributes = $.makeArray(arguments).join(' ');

element = angular.element('<input class="tag-input" ng-model="model" ti-autosize ' + attributes + '>');
$document.find('body').append(element);

$compile(element)($scope);
$scope.$digest();
}

function getTextWidth(text) {
var width, span = angular.element('<span class="tag-input"></span>');

span.text(text);
$document.find('body').append(span);
width = span.prop('offsetWidth') + 'px';

span.remove();

return width;
}

it('re-sizes the input width when its view content changes', function() {
// Arrange
var text = 'AAAAAAAAAA';
compile();

// Act
element.val(text);
element.trigger('input');

// Arrange
expect(element.css('width')).toBe(getTextWidth(text));
});

it('re-sizes the input width when its model value changes', function() {
// Arrange
var text = 'AAAAAAAAAAAAAAAAAAAA';
compile();

// Act
$scope.model = text;
$scope.$digest();

// Arrange
expect(element.css('width')).toBe(getTextWidth(text));
});

it('sets the input width as the placeholder width when the input is empty', function() {
// Arrange
compile('placeholder="Some placeholder"');

// Act
$scope.model = '';
$scope.$digest();

// Assert
expect(element.css('width')).toBe(getTextWidth('Some placeholder'));
});
});
8 changes: 0 additions & 8 deletions test/tags-input.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -501,14 +501,6 @@ describe('tags-input-directive', function() {
expect(getInput().attr('placeholder')).toBe('New tag');
});

it('sets the input size attribute to placeholder option length', function() {
// Arrange/Act
compile('placeholder="New tag"');

// Assert
expect(getInput().attr('size')).toBe('7');
});

it('sets the option given a static string', function() {
// Arrange/Act
compile('placeholder="New tag"');
Expand Down
13 changes: 12 additions & 1 deletion test/test-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,24 @@
<script type="text/javascript" src="lib/angular.js"></script>
<script type="text/javascript" src="../build/ng-tags-input.js"></script>
<link rel="stylesheet" href="../build/ng-tags-input.css"/>
<style>
.foo .tags .tag-input {
font-size: 16px;
font-weight: bold;
letter-spacing: 4px;
border-left: 3px solid;
padding-left: 3px;
}
</style>
</head>
<body ng-controller="Ctrl">
<tags-input ng-model="tags"
custom-class="foo"
placeholder="{{ placeholder.value }}"
replace-spaces-with-dashes="false"
add-on-blur="true"
allowed-tags-pattern="^[a-zA-Z0-9\s<>@\.]+$">
allowed-tags-pattern="^[a-zA-Z0-9\s<@\.]+$"
enable-editing-last-tag="true">
<auto-complete source="loadItems($query)"
debounce-delay="0"
min-length="1"
Expand Down

0 comments on commit 8abdf79

Please sign in to comment.