diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ca22b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +### Example user template template +### Example user template + +# IntelliJ project files +.idea +*.iml +out +gen +# Created by .ignore support plugin (hsz.mobi) diff --git a/readme.md b/README.md similarity index 59% rename from readme.md rename to README.md index 8990f89..71fcd87 100644 --- a/readme.md +++ b/README.md @@ -1,7 +1,50 @@ +jQuery JSON Tag Editor +====================== + +*jQuery JSON Tag Editor* is a fork of [jQuery-tagEditor](https://goodies.pixabay.com/jquery/tag-editor/demo.html), originally developed by Simon Steinberger for +[Pixabay.com](https://pixabay.com) and licensed under the [MIT license](https://opensource.org/licenses/MIT). This fork maintains the same license and adds the +[Apache License, 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +*jQuery JSON Tag Editor* adds a separation between the visual representation of a tag and the tag itself. Tags are plain JavaScript objects with a `value` attribute +that is its visual representation in the tag editor. `value` plus any other properties that the tag object may have are all stored as +[data attributes](https://developer.mozilla.org/en/docs/Web/Guide/HTML/Using_data_attributes) in each `
` element. + +So, using the `addTag` public method, this: +``` +$('#id').jsonTagEditor('addTag', '{"value": "Car", "tagType": "string", "category": "vehicle"}') +``` + +Produces the markup below: +``` +
Car
+``` + +If a string is provided as an argument then an object with only the `value` property is created. This is the case when tags are manually typed into the editor. + +Features added: +--------------- +* Tags are complex objects: what you see in the editor is the property `value`, but you can add arbitrary data to your tags +* Tag object properties are stored as data attributes in their corresponding `
` element +* A new option `maxTagLength` can ellipsify tags while keeping the original value in `data-value` +* Multiple tags when pasting a multi-line text snippet, one per line, or split by tabs + +Features removed: +----------------- +* Auto-grow width of `` editors +* Support for cut: the original feature was Window-specific but buggy as tags weren’t put in the clipboard +* `removeDuplicates`: because tags aren’t strings it was the simplest way to deal with this issue +* The `delimiter` is hard-coded to `\t` and `\n` + +__[Visit the demo page for the full documentation](https://alfonsomunozpomer.github.io/jsontageditor/demo.html).__ + +--- + +What follows are the original contents of jQuery-tagEditor’s README.md file: + jQuery-tagEditor ================ -A powerful and lightweight tag editor plugin for jQuery. +A powerful and lightweight tag editor plugin for jQuery based on jQuery-tagEditor by Pixabay Compatible with jQuery 1.7.0+ in Firefox, Safari, Chrome, Opera, Internet Explorer 8+. IE7 technically works, but no care has gone into CSS/layout bugs. Released under the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/demo.html b/demo.html index 73f1f25..113a802 100644 --- a/demo.html +++ b/demo.html @@ -2,8 +2,7 @@ jQuery tagEditor Plugin - - + @@ -60,80 +59,104 @@ #response hr { margin: 2px 0; border: 0; border-top: 1px solid #eee; border-bottom: 1px solid #fdfdfd; } /* overwrite default CSS for tiny, dark tags in demo5 */ - #demo5+.tag-editor { background: #fafafa; font-size: 12px; } - #demo5+.tag-editor .tag-editor-tag { color: #fff; background: #555; border-radius: 2px; } - #demo5+.tag-editor .tag-editor-spacer { width: 7px; } - #demo5+.tag-editor .tag-editor-delete { display: none; } + #demo5+.json-tag-editor { background: #fafafa; font-size: 12px; } + #demo5+.json-tag-editor .json-tag-editor-tag { color: #fff; background: #555; border-radius: 2px; } + #demo5+.json-tag-editor .json-tag-editor-spacer { width: 7px; } + #demo5+.json-tag-editor .json-tag-editor-delete { display: none; } /* color tags */ - .tag-editor .red-tag .tag-editor-tag { color: #c65353; background: #ffd7d7; } - .tag-editor .red-tag .tag-editor-delete { background-color: #ffd7d7; } - .tag-editor .green-tag .tag-editor-tag { color: #45872c; background: #e1f3da; } - .tag-editor .green-tag .tag-editor-delete { background-color: #e1f3da; } + .json-tag-editor .red-tag .json-tag-editor-tag { color: #c65353; background: #ffd7d7; } + .json-tag-editor .red-tag .json-tag-editor-delete { background-color: #ffd7d7; } + .json-tag-editor .green-tag .json-tag-editor-tag { color: #45872c; background: #e1f3da; } + .json-tag-editor .green-tag .json-tag-editor-delete { background-color: #e1f3da; } - +
-

tagEditor

-

A powerful and lightweight tag editor plugin for jQuery.

- Download +

JSON Tag Editor

+

A powerful and lightweight tag editor plugin for jQuery based on jQuery tagEditor.

+ Download   - View on GitHub + View on GitHub
+

All the goodness from jQuery tagEditor:

- +
+ +
+

Plus some more:

+
+ +
+
+

Overview and Features

Released under the MIT License. - Source on Github (changelog). - Compatible with jQuery 1.7.0+ in Firefox, Safari, Chrome, Opera, Internet Explorer 8+. IE7 technically works, but no care has gone into CSS/layout bugs. - tagEditor depends on accursoft's caret plugin (1.1 kB minified). + Relicensed under the Apache License, 2.0. + Source on Github. + Compatible with jQuery 1.7.0+ in Firefox, Safari, Chrome, Edge, and Internet Explorer 11+. Other or earlier versions of these browsers might work but it hasn’t been tested at all. Use at your own risk! + JSON Tag Editor, as its predecessor, depends on accursoft's caret plugin (1.1 kB minified).

+ New features: +
    +
  • Ellipsify lengthy tags
  • +
  • Add metadata or arbitrary attributes to tags
  • +
  • The text displayed by the tag can have no relation with the tag’s internal value
  • +
  • Option to disable selection
  • +
+ Common features from jQuery tagEditor:
    -
  • Lightweight: 8.5 kB of JavaScript - less than 3.2 kB gzipped
  • +
  • Lightweight: 13 kB of JavaScript - 3.5 kB gzipped
  • Edit in place tags
  • -
  • Intuitive navigation between tags with cursor keys, Tab, Shift+Tab, Enter, Pos1, End, Backspace, Del, and ESC
  • +
  • Intuitive navigation between tags with cursor keys, Tab, Shift+Tab, Enter, End, Backspace, Del, and Esc
  • Optional jQuery UI sortable
  • Optional jQuery UI autocomplete
  • -
  • Copy-paste or delete multiple selected tags
  • -
  • Duplicate tags check
  • -
  • Custom delimiter/s
  • +
  • Copy-paste or multiple tags
  • Placeholder
  • Custom style for faulty tags
  • Public methods for reading, adding and removing tags + destroy function
  • Callbacks
  • Allows tabindex for form navigation
  • -
  • Graceful degradation if JavaScript is disabled
  • +
+ Removed features from jQuery tagEditor: +
    +
  • Use the
  • +
  • Delete multiple selected tags
  • +
  • Duplicate tags check

- This plugin was developed by and for Pixabay.com - an international repository for free Public Domain images. - We have implemented this piece of software in production and we share it - in the spirit of Pixabay - freely with others. + This plugin was forked by Alfonso Muñoz-Pomer Fuentes (GitHub repository, blog) + as part of Expression Atlas (GitHub repository). + The original plugin was developed by Pixabay.com - an international repository for free Public Domain images.

Usage

- Include the stylesheet jquery.tag-editor.css in the <head> section of your HTML document - and the JavaScript file jquery.tag-editor.min.js after loading jQuery and optional jQuery UI sortable/autocomplete. + Include the stylesheet jquery.json-tag-editor.css in the <head> section of your HTML document - and the JavaScript file jquery.json-tag-editor.min.js after loading jQuery and optional jQuery UI sortable/autocomplete. Make sure to also load accursoft's caret plugin (1.1 kB minified). - tagEditor accepts settings from an object of key/value pairs, and can be assigned to any text input field or textarea. + JSON Tag Editor accepts settings from an object of key/value pairs, and can be assigned to any text input field or textarea.

-$(selector).tagEditor({key1: value1, key2: value2});
+$(selector).jsonTagEditor({key1: value1, key2: value2});
 
 // examples
 
 // assign tag editor to textarea - existing text will be used as initial tags
-$('textarea').tagEditor();
+$('textarea').jsonTagEditor();
 
 // assign tag editor to text input with initial tags
-$('input[type="text"]').tagEditor({ initialTags: ['tag1', 'tag2', 'tag3'] });
+$('input[type="text"]').jsonTagEditor({ initialTags: ['tag1', 'tag2', 'tag3'] });
 
 // use jQuery UI autocomplete
-$('#my_textarea').tagEditor({ autocomplete: { 'source': '/url/', minLength: 3 } });
+$('#my_textarea').jsonTagEditor({ autocomplete: { 'source': '/url/', minLength: 3 } });

Settings

@@ -141,18 +164,10 @@

Settings

- - - - + + - @@ -179,9 +194,8 @@

DemosBasic settings

-$('#demo1').tagEditor({
-    initialTags: ['Hello', 'World', 'Example', 'Tags'],
-    delimiter: ', ', /* space and comma */
+$('#demo1').jsonTagEditor({
+    initialTags: [{"value: "Hello"}, {"value": "World"}, {"value": "Example"}, {"value": "Tags"}],
     placeholder: 'Enter tags ...'
 });
@@ -199,7 +213,7 @@

Autocomplete

You can then pass any options that work with UI autocomplete to your tagEditor settings.

-$('#demo2').tagEditor({
+$('#demo2').jsonTagEditor({
     autocomplete: {
         delay: 0, // show suggestions immediately
         position: { collision: 'flip' }, // automatic menu position up/down
@@ -212,48 +226,50 @@ 

Autocomplete

Public methods

-$('#demo3').tagEditor({
-    initialTags: ['Hello', 'World'],
+$('#demo3').jsonTagEditor({
+    initialTags: [{"value":"Hello", {"value":"World"}],
     placeholder: 'Enter tags ...'
 });

- getTags - addTag 'example' - removeTag 'example' + getTags + addTag 'example' + addTag {"value":"JSON example"} + removeTag 'example' Remove all tags - destroy - + destroy +

 // actions on button clicks
 
 // getTags
-alert( $('#demo3').tagEditor('getTags')[0].tags );
+alert( $('#demo3').jsonTagEditor('getTags')[0].tags );
 
 // addTag
-$('#demo3').tagEditor('addTag', 'example');
+$('#demo3').jsonTagEditor('addTag', 'example');
+$('#demo3').jsonTagEditor('addTag', 'example');
 
 // removeTag
-$('#demo3').tagEditor('removeTag', 'example');
+$('#demo3').jsonTagEditor('removeTag', 'example');
 
 // Remove all tags
 function() {
-    var tags = $('#demo3').tagEditor('getTags')[0].tags;
-    for (i = 0; i < tags.length; i++) { $('#demo3').tagEditor('removeTag', tags[i]); }
+    var tags = $('#demo3').jsonTagEditor('getTags')[0].tags;
+    for (i = 0; i < tags.length; i++) { $('#demo3').jsonTagEditor('removeTag', tags[i]); }
 }
 // working shortcut for removing all tags
-// $('#demo3').next('.tag-editor').find('.tag-editor-delete').click();
+// $('#demo3').next('.json-tag-editor').find('.json-tag-editor-delete').click();
 
 // destroy
-$('#demo3').tagEditor('destroy');
+$('#demo3').jsonTagEditor('destroy');
 
 // re-init editor
-$('#demo3').tagEditor({ placeholder: 'Enter tags ...' });
+$('#demo3').jsonTagEditor({ placeholder: 'Enter tags ...' });

Callbacks

-$('#demo4').tagEditor({
+$('#demo4').jsonTagEditor({
     initialTags: ['Hello', 'World'],
     placeholder: 'Enter tags ...',
     onChange: function(field, editor, tags) {
@@ -280,22 +296,22 @@ 

Custom style and clickDelete

Use right mouse click or Ctrl+left click to delete tags.

-$('#demo5').tagEditor({
+$('#demo5').jsonTagEditor({
     clickDelete: true,
-    initialTags: [ ... ],
+    initialTags: [ ],
     placeholder: 'Enter tags ...'
 });
 /* overwrite default CSS for tiny, dark tags */
 
-#demo5+.tag-editor { background: #fafafa; font-size: 12px; }
-#demo5+.tag-editor .tag-editor-tag {
+#demo5+.json-tag-editor { background: #fafafa; font-size: 12px; }
+#demo5+.json-tag-editor .json-tag-editor-tag {
     color: #fff; background: #555;
     border-radius: 2px;
 }
-#demo5+.tag-editor .tag-editor-spacer { width: 7px; }
-#demo5+.tag-editor .tag-editor-delete { display: none; }
+#demo5+.json-tag-editor .json-tag-editor-spacer { width: 7px; } +#demo5+.json-tag-editor .json-tag-editor-delete { display: none; }

This jQuery plugin was designed with custom styling in mind. In this example we've enabled the clickDelete feature while hiding all delete icons. Both options may be used at the same time, as well. By fiddling around with the default stylesheet, you can achieve almost any desired look for your tag Editor. @@ -307,7 +323,7 @@

Custom CSS classes for tags

Using the onChange callback for adding custom CSS classes to specific tags.

-$('#demo6').tagEditor({
+$('#demo6').jsonTagEditor({
     initialTags: ['custom', 'class', 'red', 'green', 'demo'],
     onChange: tag_classes
 });
@@ -315,14 +331,14 @@ 

Custom CSS classes for tags

function tag_classes(field, editor, tags) { $('li', editor).each(function(){ var li = $(this); - if (li.find('.tag-editor-tag').html() == 'red') li.addClass('red-tag'); - else if (li.find('.tag-editor-tag').html() == 'green') li.addClass('green-tag') + if (li.find('.json-tag-editor-tag').html() == 'red') li.addClass('red-tag'); + else if (li.find('.json-tag-editor-tag').html() == 'green') li.addClass('green-tag') else li.removeClass('red-tag green-tag'); }); } // first assign tag classes after initializing tagEditor; onChange is not called on init -tag_classes(null, $('#demo6').tagEditor('getTags')[0].editor);
+tag_classes(null, $('#demo6').jsonTagEditor('getTags')[0].editor);

In the onChange callback we iterate over all tags and assign custom CSS classes where appropriate. @@ -330,9 +346,9 @@

Custom CSS classes for tags

<ul>
     <li>
-        <div class="tag-editor-spacer"></div>
-        <div class="tag-editor-tag">Tag content</div>
-        <div class="tag-editor-delete"><i></i></div>
+        <div class="json-tag-editor-spacer"></div>
+        <div class="json-tag-editor-tag">Tag content</div>
+        <div class="json-tag-editor-delete"><i></i></div>
     </li>
     [...]
 </ul>
@@ -353,25 +369,27 @@

Custom CSS classes for tags

-

Please report any bugs and issues at the GitHub repositiory.

-

This software is released as Open Source under the MIT License by Simon Steinberger / Pixabay.com.

+

Please report any bugs and issues at the GitHub repositiory.

+

This software is released as Open Source under the Apache License, 2.0 by Alfonso Muñoz-Pomer Fuentes. +

Fork of an original project by Simon Steinberger / Pixabay.com.

- About Us - Blog - More Goodies - © Pixabay.com / Simon Steinberger / Hans Braxmeier + jQuery tagEditor is © Pixabay.com / Simon Steinberger / Hans Braxmeier
- - + + - + diff --git a/jquery.json-tag-editor.css b/jquery.json-tag-editor.css new file mode 100644 index 0000000..edaa9cf --- /dev/null +++ b/jquery.json-tag-editor.css @@ -0,0 +1,62 @@ +.noselect { + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Chrome/Safari/Opera */ + -khtml-user-select: none; /* Konqueror */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + not supported by any browser */ +} + +.json-tag-editor-spacer::selection { + background: transparent; /* WebKit/Blink Browsers */ +} +.json-tag-editor-spacer::-moz-selection { + background: transparent; /* Gecko Browsers */ +} + +/* surrounding tag container */ +.json-tag-editor { + list-style-type: none; padding: 0 5px 0 0; margin: 0; overflow: hidden; border: 1px solid #eee; cursor: text; + font: normal 14px sans-serif; color: #555; background: #fff; line-height: 20px; +} + +/* core styles usually need no change */ +.json-tag-editor li { display: block; float: left; overflow: hidden; margin: 3px 0; } +.json-tag-editor div { float: left; padding: 0 4px; } +.json-tag-editor .placeholder { padding: 0 8px; color: #bbb; } +.json-tag-editor .json-tag-editor-spacer { padding: 0; width: 8px; overflow: hidden; color: transparent; background: none; } +.json-tag-editor input { + vertical-align: inherit; border: 0; outline: none; padding: 0; margin: 0; cursor: text; + font-family: inherit; font-weight: inherit; font-size: inherit; font-style: inherit; + box-shadow: none; background: none; color: #444; +} +/* hide original input field or textarea visually to allow tab navigation */ +.json-tag-editor-hidden-src { position: absolute !important; left: -99999px; } +/* hide IE10 "clear field" X */ +.json-tag-editor ::-ms-clear { display: none; } + +/* tag style */ +.json-tag-editor .json-tag-editor-tag { + padding-left: 5px; color: #46799b; background: #e0eaf1; white-space: nowrap; + overflow: hidden; cursor: pointer; border-radius: 2px 0 0 2px; +} + +/* delete icon */ +.json-tag-editor .json-tag-editor-delete { background: #e0eaf1; cursor: pointer; border-radius: 0 2px 2px 0; padding-left: 3px; padding-right: 4px; } +.json-tag-editor .json-tag-editor-delete i { line-height: 18px; display: inline-block; } +.json-tag-editor .json-tag-editor-delete i:before { font-size: 16px; color: #8ba7ba; content: "×"; font-style: normal; } +.json-tag-editor .json-tag-editor-delete:hover i:before { color: #d65454; } +.json-tag-editor .json-tag-editor-tag.active+.json-tag-editor-delete, .json-tag-editor .json-tag-editor-tag.active+.json-tag-editor-delete i { visibility: hidden; cursor: text; } + +.json-tag-editor .json-tag-editor-tag.active { background: none !important; } + +/* jQuery UI autocomplete - code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css */ +.ui-autocomplete { position: absolute; top: 0; left: 0; cursor: default; font-size: 14px; } +.ui-front { z-index: 9999; } +.ui-menu { list-style: none; padding: 1px; margin: 0; display: block; outline: none; } +.ui-menu .ui-menu-item a { text-decoration: none; display: block; padding: 2px .4em; line-height: 1.4; min-height: 0; /* support: IE7 */ } +.ui-widget-content { border: 1px solid #bbb; background: #fff; color: #555; } +.ui-widget-content a { color: #46799b; } +.ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { background: #e0eaf1; } +.ui-helper-hidden-accessible { display: none; } diff --git a/jquery.json-tag-editor.js b/jquery.json-tag-editor.js new file mode 100644 index 0000000..63b2d2b --- /dev/null +++ b/jquery.json-tag-editor.js @@ -0,0 +1,693 @@ +"use strict"; + +(function($){ + + $.fn.jsonTagEditor = function(options, val, blur) { + + function escape(tag) { + return tag.replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + function validateTagArray(json) { + try { + return JSON.parse(json).map(function(tagArrayElement) { + return validate(JSON.stringify(tagArrayElement)); + }).filter(function(element) { + return element; + }); + } catch (e) { + return json.split(o.dregex).map(function(tagArrayElement) { + return tagArrayElement.trim().length > 0 ? {value: tagArrayElement.trim()} : false; + }).filter(function(element) { + return element; + }); + } + } + + function validateParsedTag(tagObject) { + return (tagObject.hasOwnProperty('value') && typeof tagObject.value === 'string' && tagObject.value.trim().length > 0); + } + + function validate(tag) { + try { + var parsedTag = JSON.parse(tag); + if (validateParsedTag(parsedTag)) { + return parsedTag; + } + } catch (e) {} + + return String(tag).trim().length > 0 ? {value: String(tag).trim()} : false; + } + + function ellipsify(str, maxLength) { + return maxLength > -1 && str.length > maxLength ? str.substring(0, maxLength - 1) + "…" : str; + } + + function deepEquals(arr1, arr2) { + return $(arr1).not(arr2).length === 0 && $(arr2).not(arr1).length === 0; + } + + + // Build options dictionary with default values + var blurResult, + o = $.extend({}, $.fn.jsonTagEditor.defaults, options), + selector = this; + + // Store regex and default delimiter in options for later use + o.delimiter = '\t\n'; + o.dregex = new RegExp('[' + o.delimiter + ']', 'g'); + + + // Public methods + if (typeof options === 'string') { + // Depending on selector, response may contain tag lists of multiple editor instances + var response = []; + selector.each(function() { + // The editor is the next sibling to the hidden, original element + var el = $(this), + o = el.data('options'), + ed = el.next('.json-tag-editor'); + + //switch (options) {} + + switch (options) { + case 'getTags': + response.push({field: el[0], editor: ed, tags: ed.data('tags')}); + break; + + case 'addTag': + if (o.maxTags && ed.data('tags').length >= o.maxTags) { + return false; + } + + // The tag is placed in an which will be removed, and its value placed inside the json-tag-editor-tag
, after calling blur() + $('
  • ') + .append('
     ' + o.delimiter[0] + '
    ') + .append('
    ') + .append('
    ') + .appendTo(ed).find('.json-tag-editor-tag') + .append('') + .addClass('active').find('input').val(val).blur(); + + if (!blur) { + ed.click(); + } + else { + // setPlaceHolder() isn’t declared in this context and .placeholder is removed explicitly + $('.placeholder', ed).remove(); + } + break; + + case 'removeTag': + // Trigger delete on matching tag, then click editor to create a new tag + $('.json-tag-editor-tag', ed).filter(function() { + return $(this).get(0).dataset.value === val; + }).closest('li').find('.json-tag-editor-delete').click(); + + if (!blur){ + ed.click(); + } + break; + + case 'destroy': + el.removeClass('json-tag-editor-hidden-src').removeData('options').off('focus.json-tag-editor').next('.json-tag-editor').remove(); + break; + + default: + return this; + } + }); + + return options === 'getTags' ? response : this; + } + // End of public methods + + + // Delete selected tags on backspace, delete + if (window.getSelection) { + $(document).off('keydown.json-tag-editor').on('keydown.json-tag-editor', function(e) { + if (e.which === 8 || e.which === 46) { + try { + var sel = getSelection(), + el = document.activeElement.tagName === 'BODY' ? $(sel.getRangeAt(0).startContainer.parentNode).closest('.json-tag-editor') : 0; + } + catch(e) { + el = 0; + } + + if (sel.rangeCount > 0 && el && el.length) { + $('.json-tag-editor-tag', el).each(function() { + if (sel.containsNode($(this).get(0))) { + $(this).closest('li').find('.json-tag-editor-delete').click(); + } + }); + return false; + } + + } + }); + } + + // Create a tagEditor for each of the matched elements (usually an or a
    initialTags[]Initial tags as an array of strings.
    maxTagsnullMaximum number of allowed tags.
    maxLength50maxlength attribute of the tag input field.
    delimiter',;' -

    - Required string of delimiters - characters for separating tags. - The first character is used as default delimiter in the (hidden) original field. -

    -
    maxTagLength-1Maximum length of the displayed tag text. Longer tags are ellipsified. Any negative will have the whole tag value displayed.
    noSelectfalseDisable selection on the tag editor.
    placeholder''Placeholder text for empty tag editor.
    forceLowercasetrueLowercase all tags.
    removeDuplicatestrueAutomatically remove duplicate tags.
    clickDeletefalseDelete tags on right click and on Ctrl+click.
    animateDelete175Animate duration for deletion of tags in milliseconds. Set to 0 for non-animated removal.
    sortabletrueIf jQuery UI sortable is available and this option is set to true, tags are sortable by drag and drop.