Skip to content

Commit

Permalink
Merge pull request #6 from elo7/EM-106487
Browse files Browse the repository at this point in the history
Create validations based on maxlength attribute and create a new callback
  • Loading branch information
Aline Lee authored Aug 22, 2017
2 parents 07e0002 + 9d4d44d commit c33ac62
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 58 deletions.
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# tag-amd

Transform an input into a tag container.

## Dependencies

tag-amd depends on an AMD loader (we recommend [async-define](http://elo7.github.io/async-define/)) and on [doc-amd](http://elo7.github.io/doc-amd/).

## Installation

You can use either `bower` or `npm` (preferred) to install it into your project:

```
bower install tag-amd
npm install elo7-tag-amd
```

In the download package, there will be a JavaScript file (`dist/tag.min.js`) and a CSS file (`dist/tag.min.css`). The CSS file is optional; you may use it as a reference for your own styling.

## Usage

In your HTML file, import the library and its dependencies (assuming you are using *async-define*):

```html
<script src='async-define.js'></script>
<script src='events-amd.js' async></script>
<script src='doc.js' async></script>
<script src='tag.js' async></script>
```

**Important:** `events-amd` its a `doc-amd` [dependency](https://github.com/elo7/doc-amd/#dependencies).

Create a form control for your user to type the tags using either `<input>` or `<textarea>`:

```html
<label for='my-tag-field'>Type your tags here</label>
<input id='my-tag-field'>
```

**Important:** do **not** put the tag field inside a `<label>` element!

Then, in your own JavaScript files, transform the input into a tag field:

```javascript
define(['tag'], function(tag) {
tag.tagify('#my-tag-field');
});
```

You may optionally specify callbacks by passing an object as second argument:

```javascript
define(['tag'], function(tag) {
tag.tagify('#my-tag-field', {
added: function(addedTags) {
console.log('Hey! Here is an array of new tags for you', addedTags);
}
});
});
```

## API

```javascript
tag.tagify(cssSelector, options)
```

where `options` is an object that may contain the following callbacks:

- `added(addedTags)`: called when one or more tags are added to the list of tags; receives an array with the new tags as argument
- `removed(removedTag)`: called when a tag is removed from the list; receives the removed tag as argument
- `errorAlreadyExists(tagInput)`: called when the user tries to add a tag that already exists in the list; receives the input as argument
- `errorCleared(tagInput)`: called when the user edits the tag field after an error; receives the input as argument
- `maxlengthExceeded()`: called when the input's maxlength is reached after adding a tag, **not** when the user is typing the tags; receives no arguments

## License

tag-amd is released under the [BSD license](https://github.com/elo7/tag-amd/blob/master/LICENSE). Have at it.

---

Copyright ©️ 2017 Elo7# tag-amd
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tag-amd",
"version": "1.1.2",
"version": "1.2.0",
"description": "Transform an input into a tag field",
"main": "tag.js",
"authors": [
Expand Down
2 changes: 1 addition & 1 deletion dist/tag.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 19 additions & 1 deletion dist/tag.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
{
"name": "elo7-tag-amd",
"version": "1.1.2",
"version": "1.2.0",
"description": "Transform an input into a tag field",
"main": "tag.js",
"scripts": {
"test": "bash test.sh",
"test:server": "node test/acceptance/test_server.js"
"test:server": "node test/acceptance/test_server.js",
"build:js": "minify src/tag.js -o dist/tag.min.js --compress drop_console=true --mangle --screw-ie8",
"build:css": "minify src/tag.css -o dist/tag.min.css",
"build": "npm run build:js && npm run build:css"
},
"repository": {
"type": "git",
Expand All @@ -30,6 +33,7 @@
"devDependencies": {
"bower": "1.7.9",
"express": "4.15.4",
"minifier": "0.8.1",
"mocha": "3.5.0",
"mocha-phantomjs": "4.1.0",
"phantomjs-prebuilt": "2.1.14",
Expand Down
4 changes: 4 additions & 0 deletions src/tag.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
border-radius: 3px;
position: relative;
padding-right: 1.5em;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.tags-container .tag.selected {
Expand Down
70 changes: 55 additions & 15 deletions src/tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ define('tag', ['doc'], function($) {
BACKSPACE = 8,
DELETE = 46,
LEFT_KEY = 37,
RIGHT_KEY = 39;
RIGHT_KEY = 39,
EXACT_MAXLENGTH_ATTR = 'data-tag-exact-maxlength';

var isKeyPressed = function(event, key) {
return event.which === key || event.keyCode === key;
Expand All @@ -19,7 +20,7 @@ define('tag', ['doc'], function($) {
});
};

var Tag = function($element) {
var Tag = function($element, options) {
var tags = [],
selectedTag = null,
isRequired = !$element.filter('required').isEmpty(),
Expand All @@ -40,10 +41,14 @@ define('tag', ['doc'], function($) {
});

$container.on('keydown', function(e) {
var hadError = $element.hasClass('error');
$element.removeClass('error');
if (isAnyOfTheseKeysPressed(e, [ENTER, COMMA, TAB])) {
if (hadError && options && options.errorCleared && options.errorCleared.call) {
options.errorCleared.call(null, $element.first());
}
if ($element.val() !== '' && isAnyOfTheseKeysPressed(e, [ENTER, COMMA, TAB])) {
e.preventDefault();
addTags();
addTags(e);
} else if (selectedTag !== null && isAnyOfTheseKeysPressed(e, [BACKSPACE, DELETE])) {
removeTag(selectedTag);
selectedTag = null;
Expand Down Expand Up @@ -85,32 +90,53 @@ define('tag', ['doc'], function($) {
return previousArray;
};

var addTags = function() {
var tagsToAdd = $element.val().trim().split(/\s*,\s*/).filter(function(tag) {
var addTags = function(event) {
var value = $element.val() || $element.text();
var tagsToAdd = value.trim().split(/\s*,\s*/).filter(function(tag) {
return tag.length > 0 && tags.indexOf(tag.trim()) < 0;
}).reduce(filterUniques, []);
if(tagsToAdd.length > 0) {
for(var i = 0; i < tagsToAdd.length; i++) {
addTag(tagsToAdd[i]);
addTag(tagsToAdd[i], event);
}
$element.val('');
} else {
if (options && options.added && options.added.call) {
options.added.call(null, tagsToAdd);
}
} else if (value.trim().length > 0) {
$element.addClass('error');
if (options && options.errorAlreadyExists && options.errorAlreadyExists.call) {
options.errorAlreadyExists.call(null, $element.first());
}
}
};

var addTag = function(tag) {
var addTag = function(tag, event) {
var $li = $(document.createElement('li')),
$input = $(document.createElement('input')),
$closeButton = $(document.createElement('button'));
$closeButton.attr('type', 'button').addClass('close').html('&times;');
$closeButton = $(document.createElement('button')),
elementMaxlength = $element.attr('maxlength');
$closeButton.attr('type', 'button').attr('tabindex', '-1').addClass('close').html('&times;');
$input.attr('type', 'hidden').attr('name', $element.attr('name')).val(tag);
$li.text(tag);
$li.addClass('tag').append($input.first());
$li.append($closeButton.first());
$tagList.first().insertBefore($li.first(), $inputContainer.first());
$element.removeAttr('required');

if (elementMaxlength) {
var computedMaxlength = parseInt(elementMaxlength, 10) - tag.length - 1;
if (computedMaxlength < 0) {
$element.attr(EXACT_MAXLENGTH_ATTR, 'true');
$element.attr('maxlength', 0);
} else {
$element.attr('maxlength', computedMaxlength);
}
if (computedMaxlength <= 0 && event && options && options.maxlengthExceeded && options.maxlengthExceeded.call) {
options.maxlengthExceeded.call(null);
}
}

$closeButton.on('click', function(e) {
e.stopImmediatePropagation();
var index = tags.indexOf($input.val());
Expand All @@ -130,13 +156,27 @@ define('tag', ['doc'], function($) {
};

var removeTag = function(index) {
if(tags.splice(index, 1).length > 0) {
var tag = $tagList.find('.tag').els[index];
var tagToRemove = tags[index];
if (tags.splice(index, 1).length > 0) {
var tag = $tagList.find('.tag').els[index],
elementMaxlength = $element.attr('maxlength');
$(tag).removeItem();
if (elementMaxlength) {
var numericMaxlength = parseInt(elementMaxlength, 10),
computedMaxlength = numericMaxlength + tagToRemove.length + 1;
if (numericMaxlength === 0 && $element.attr(EXACT_MAXLENGTH_ATTR)) {
computedMaxlength--;
$element.removeAttr(EXACT_MAXLENGTH_ATTR);
}
$element.attr('maxlength', computedMaxlength);
}
}
if (isRequired && tags.length === 0) {
$element.attr('required', '');
}
if (options && options.removed && options.removed.call) {
options.removed.call(null, tagToRemove);
}
};

addTags();
Expand All @@ -152,12 +192,12 @@ define('tag', ['doc'], function($) {
};
};

var tagify = function(selector) {
var tagify = function(selector, options) {
var $element = $(selector);
if ($element.isEmpty()) {
return null;
}
return new Tag($element);
return new Tag($element, options);
};

return {
Expand Down
2 changes: 1 addition & 1 deletion test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ node test/acceptance/test_server.js &
NODE_PID=$!
sleep 3

./node_modules/mocha-phantomjs/bin/mocha-phantomjs -p ./node_modules/.bin/phantomjs -R spec "http://localhost:3000/index.html";
./node_modules/mocha-phantomjs/bin/mocha-phantomjs -p ./node_modules/.bin/phantomjs -R spec "http://localhost:4000/index.html";
Loading

0 comments on commit c33ac62

Please sign in to comment.