Skip to content

added readonly feature #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ <h3>Settings</h3>
<tr><td>animateDelete</td><td><i>175</i></td><td>Animate duration for deletion of tags in milliseconds. Set to 0 for non-animated removal.</td></tr>
<tr><td>sortable</td><td><i>true</i></td><td>If <a href="https://jqueryui.com/sortable/">jQuery UI sortable</a> is available and this option is set to <span class="inline-code">true</span>, tags are sortable by drag and drop.</td></tr>
<tr><td>autocomplete</td><td><i>null</i></td><td><a href="https://jqueryui.com/autocomplete/">jQuery UI autocomplete</a> options as key/value pairs object. If provided, jQuery UI autocomplete must be loaded additionally.</td></tr>
<tr><td>readOnly</td><td><i>false</i></td><td>Readonly mode: only displays tags.</td></tr>

<tr><td colspan="3">&nbsp;</td></tr>
<tr><th>Callbacks</th><th colspan="2"></th></tr>
Expand Down Expand Up @@ -342,6 +343,20 @@ <h4>Custom CSS classes for tags</h4>
This is just an exampe of what the onChange callback may be used for. Inside of it, <span class="inline-code">addTag</span> and <span class="inline-code">removeTag</span> may be called to dynamically change the current list of tags.
</p>

<h4>Readonly</h4>
<pre>
$(<b>'#demo7'</b>).tagEditor({
initialTags: [<b>'Hello'</b>, <b>'World'</b>, <b>'Example'</b>, <b>'Tags'</b>],
delimiter: <b>', '</b>, <i>/* space and comma */</i>
placeholder: <b>'Enter tags ...'</b>,
readOnly: <b>true</b>
});</pre>

<div style="margin:0 0 1.2em">
<p>The tagEditor is in read only mode.</p>
<textarea id="demo7"></textarea>
</div>

<div style="margin:40px 0;overflow:hidden">
<span id="github_social"></span>
<div style="float:left;margin-right:35px">
Expand Down Expand Up @@ -445,6 +460,8 @@ <h4>Custom CSS classes for tags</h4>
}
$('#demo6').tagEditor({ initialTags: ['custom', 'class', 'red', 'green', 'demo'], onChange: tag_classes });
tag_classes(null, $('#demo6').tagEditor('getTags')[0].editor); // or editor == $('#demo6').next()

$('#demo7').tagEditor({ initialTags: ['Hello', 'World', 'Example', 'Tags'], delimiter: ', ', placeholder: 'Enter tags ...', readOnly: true }).css('display', 'block').attr('readonly', true);
});

if (~window.location.href.indexOf('http')) {
Expand Down
1 change: 1 addition & 0 deletions jquery.tag-editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
padding-left: 5px; color: #46799b; background: #e0eaf1; white-space: nowrap;
overflow: hidden; cursor: pointer; border-radius: 2px 0 0 2px;
}
.tag-editor .tag-editor-tag.readonly { border-radius: 2px; cursor: auto; }

/* delete icon */
.tag-editor .tag-editor-delete { background: #e0eaf1; cursor: pointer; padding-right: 5px; border-radius: 0 2px 2px 0; }
Expand Down
171 changes: 87 additions & 84 deletions jquery.tag-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,89 +104,91 @@
set_placeholder();
}

ed.click(function(e, closest_tag){
var d, dist = 99999, loc;

// do not create tag when user selects tags by text selection
if (window.getSelection && getSelection() != '') return;

if (o.maxTags && ed.data('tags').length >= o.maxTags) { ed.find('input').blur(); return false; }

blur_result = true
$('input:focus', ed).blur();
if (!blur_result) return false;
blur_result = true

// always remove placeholder on click
$('.placeholder', ed).remove();
if (closest_tag && closest_tag.length)
loc = 'before';
else {
// calculate tag closest to click position
$('.tag-editor-tag', ed).each(function(){
var tag = $(this), to = tag.offset(), tag_x = to.left, tag_y = to.top;
if (e.pageY >= tag_y && e.pageY <= tag_y+tag.height()) {
if (e.pageX < tag_x) loc = 'before', d = tag_x - e.pageX;
else loc = 'after', d = e.pageX - tag_x - tag.width();
if (d < dist) dist = d, closest_tag = tag;
}
});
}

if (loc == 'before') {
$(new_tag).insertBefore(closest_tag.closest('li')).find('.tag-editor-tag').click();
} else if (loc == 'after')
$(new_tag).insertAfter(closest_tag.closest('li')).find('.tag-editor-tag').click();
else // empty editor
$(new_tag).appendTo(ed).find('.tag-editor-tag').click();
return false;
});

ed.on('click', '.tag-editor-delete', function(e){
// delete icon is hidden when input is visible; place cursor near invisible delete icon on click
if ($(this).prev().hasClass('active')) { $(this).closest('li').find('input').caret(-1); return false; }

var li = $(this).closest('li'), tag = li.find('.tag-editor-tag');
if (o.beforeTagDelete(el, ed, tag_list, tag.html()) === false) return false;
tag.addClass('deleted').animate({width: 0}, o.animateDelete, function(){ li.remove(); set_placeholder(); });
update_globals();
return false;
});

// delete on right mouse click or ctrl+click
if (o.clickDelete)
ed.on('mousedown', '.tag-editor-tag', function(e){
if (e.ctrlKey || e.which > 1) {
var li = $(this).closest('li'), tag = li.find('.tag-editor-tag');
if (o.beforeTagDelete(el, ed, tag_list, tag.html()) === false) return false;
tag.addClass('deleted').animate({width: 0}, o.animateDelete, function(){ li.remove(); set_placeholder(); });
update_globals();
return false;
}
});

ed.on('click', '.tag-editor-tag', function(e){
// delete on right click or ctrl+click -> exit
if (o.clickDelete && (e.ctrlKey || e.which > 1)) return false;

if (!$(this).hasClass('active')) {
var tag = $(this).html();
// guess cursor position in text input
var left_percent = Math.abs(($(this).offset().left - e.pageX)/$(this).width()), caret_pos = parseInt(tag.length*left_percent),
input = $(this).html('<input type="text" maxlength="'+o.maxLength+'" value="'+escape(tag)+'">').addClass('active').find('input');
input.data('old_tag', tag).tagEditorInput().focus().caret(caret_pos);
if (o.autocomplete) {
var aco = $.extend({}, o.autocomplete);
// extend user provided autocomplete select method
var ac_select = 'select' in aco ? o.autocomplete.select : '';
aco.select = function(e, ui){ if (ac_select) ac_select(e, ui); setTimeout(function(){
ed.trigger('click', [$('.active', ed).find('input').closest('li').next('li').find('.tag-editor-tag')]);
}, 20); };
input.autocomplete(aco);
}
}
return false;
});
if (!o.readOnly) {
ed.click(function(e, closest_tag){
var d, dist = 99999, loc;

// do not create tag when user selects tags by text selection
if (window.getSelection && getSelection() != '') return;

if (o.maxTags && ed.data('tags').length >= o.maxTags) { ed.find('input').blur(); return false; }

blur_result = true
$('input:focus', ed).blur();
if (!blur_result) return false;
blur_result = true

// always remove placeholder on click
$('.placeholder', ed).remove();
if (closest_tag && closest_tag.length)
loc = 'before';
else {
// calculate tag closest to click position
$('.tag-editor-tag', ed).each(function(){
var tag = $(this), to = tag.offset(), tag_x = to.left, tag_y = to.top;
if (e.pageY >= tag_y && e.pageY <= tag_y+tag.height()) {
if (e.pageX < tag_x) loc = 'before', d = tag_x - e.pageX;
else loc = 'after', d = e.pageX - tag_x - tag.width();
if (d < dist) dist = d, closest_tag = tag;
}
});
}

if (loc == 'before') {
$(new_tag).insertBefore(closest_tag.closest('li')).find('.tag-editor-tag').click();
} else if (loc == 'after')
$(new_tag).insertAfter(closest_tag.closest('li')).find('.tag-editor-tag').click();
else // empty editor
$(new_tag).appendTo(ed).find('.tag-editor-tag').click();
return false;
});

ed.on('click', '.tag-editor-delete', function(e){
// delete icon is hidden when input is visible; place cursor near invisible delete icon on click
if ($(this).prev().hasClass('active')) { $(this).closest('li').find('input').caret(-1); return false; }

var li = $(this).closest('li'), tag = li.find('.tag-editor-tag');
if (o.beforeTagDelete(el, ed, tag_list, tag.html()) === false) return false;
tag.addClass('deleted').animate({width: 0}, o.animateDelete, function(){ li.remove(); set_placeholder(); });
update_globals();
return false;
});

// delete on right mouse click or ctrl+click
if (o.clickDelete)
ed.on('mousedown', '.tag-editor-tag', function(e){
if (e.ctrlKey || e.which > 1) {
var li = $(this).closest('li'), tag = li.find('.tag-editor-tag');
if (o.beforeTagDelete(el, ed, tag_list, tag.html()) === false) return false;
tag.addClass('deleted').animate({width: 0}, o.animateDelete, function(){ li.remove(); set_placeholder(); });
update_globals();
return false;
}
});

ed.on('click', '.tag-editor-tag', function(e){
// delete on right click or ctrl+click -> exit
if (o.clickDelete && (e.ctrlKey || e.which > 1)) return false;

if (!$(this).hasClass('active')) {
var tag = $(this).html();
// guess cursor position in text input
var left_percent = Math.abs(($(this).offset().left - e.pageX)/$(this).width()), caret_pos = parseInt(tag.length*left_percent),
input = $(this).html('<input type="text" maxlength="'+o.maxLength+'" value="'+tag+'">').addClass('active').find('input');
input.data('old_tag', tag).tagEditorInput().focus().caret(caret_pos);
if (o.autocomplete) {
var aco = $.extend({}, o.autocomplete);
// extend user provided autocomplete select method
var ac_select = 'select' in aco ? o.autocomplete.select : '';
aco.select = function(e, ui){ if (ac_select) ac_select(e, ui); setTimeout(function(){
ed.trigger('click', [$('.active', ed).find('input').closest('li').next('li').find('.tag-editor-tag')]);
}, 20); };
input.autocomplete(aco);
}
}
return false;
});
}

// helper: split into multiple tags, e.g. after paste
function split_cleanup(input){
Expand Down Expand Up @@ -334,7 +336,7 @@
if (tag) {
if (o.forceLowercase) tag = tag.toLowerCase();
tag_list.push(tag);
ed.append('<li><div class="tag-editor-spacer">&nbsp;'+o.delimiter[0]+'</div><div class="tag-editor-tag">'+escape(tag)+'</div><div class="tag-editor-delete"><i></i></div></li>');
ed.append('<li><div class="tag-editor-spacer">&nbsp;'+o.delimiter[0]+'</div><div class="tag-editor-tag'+(o.readOnly?' readonly':'')+'">'+escape(tag)+'</div>'+(!o.readOnly?'<div class="tag-editor-delete"><i></i></div>':'')+'</li>');
}
}
update_globals(true); // true -> no onChange callback
Expand All @@ -359,6 +361,7 @@
animateDelete: 175,
sortable: true, // jQuery UI sortable
autocomplete: null, // options dict for jQuery UI autocomplete
readOnly : false, // true to disable tag editing

// callbacks
onChange: function(){},
Expand Down