I have been working on a formatting toolbar for a textarea, much like the toolbar for writing questions on this very site (with the bold, italic, underline buttons). The only difference is that I need the buttons to make the text actually bold not just add a bold tag. What I need is similar to the Instructables.com rich comment editor, or the newgrounds.com comment editor, or google docs. Does anyone have a clue how I can make some text in a textarea show up bold, italic, or underlined but keep the rest of it?
There are lots of implementations of javascript WYSIWYG eitors. FCKEditor (now CKEditor) was one of the first. TinyMCE was popular for a long time. I've recently used TinyEditor.
There's also nicedit, 10 editors listed and compared here, not to mention the answers previously provided here on SO to the question of what's available.
An they don't have to produce HTML - there are also editors producing Wiki markup
Why reinvent the wheel?
I found how to create own Rich Text Editor just like this :
(function ($) {
if (typeof $.fn.rte === "undefined") {
var defaults = {
content_css_url: "rte.css",
media_url: "images",
dot_net_button_class: null,
max_height: 350
};
$.fn.rte = function (options) {
$.fn.rte.html = function (iframe) {
return iframe.contentWindow.document.getElementsByTagName("body")[0].innerHTML;
};
var opts = $.extend(defaults, options);
return this.each(function () {
var textarea = $(this);
var iframe;
var myID = textarea.attr("id");
var settings = $.meta ? $.extend({}, opts, textarea.data()) : opts;
var sel = {
cmdBtnSelector: "." + settings.dot_net_button_class
};
function enableDesignMode() {
var content = textarea.val();
if ($.trim(content) == '') {
content = '<br />';
}
if (iframe) {
textarea.hide();
$(iframe).contents().find("body").html(content);
$(iframe).show();
$("#toolbar-" + myID).remove();
textarea.before(toolbar());
return true;
}
iframe = document.createElement("iframe");
iframe.frameBorder = 0;
iframe.frameMargin = 0;
iframe.framePadding = 0;
iframe.height = 200;
if (textarea.attr('class')) iframe.className = textarea.attr('class');
if (textarea.attr('id')) iframe.id = myID;
if (textarea.attr('name')) iframe.title = textarea.attr('name');
textarea.after(iframe);
var css = "";
if (settings.content_css_url) {
css = "<link type='text/css' rel='stylesheet' href='" + settings.content_css_url + "' />";
}
var doc = "<html><head>" + css + "</head><body class='frameBody'>" + content + "</body></html>";
tryEnableDesignMode(doc, function () {
$("#toolbar-" + myID).remove();
textarea.before(toolbar());
textarea.hide();
});
}
function tryEnableDesignMode(doc, callback) {
if (!iframe) {
return false;
}
try {
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(doc);
iframe.contentWindow.document.close();
} catch (error) {
console.log(error)
}
if (document.contentEditable) {
iframe.contentWindow.document.designMode = "On";
callback();
return true;
} else if (document.designMode != null) {
try {
iframe.contentWindow.document.designMode = "on";
callback();
return true;
} catch (error) {
console.log(error)
}
}
setTimeout(function () {
tryEnableDesignMode(doc, callback)
}, 250);
return false;
}
function disableDesignMode(submit) {
var content = $(iframe).contents().find("body").html();
if ($(iframe).is(":visible")) {
textarea.val(content);
}
if (submit !== true) {
textarea.show();
$(iframe).hide();
}
}
function toolbar() {
var tb = $("<div class='rte-toolbar' id='toolbar-" + myID + "'><div>\
<p>\
<select>\
<option value=''>Block style</option>\
<option value='p'>Paragraph</option>\
<option value='h3'>Title</option>\
</select>\
</p>\
<p>\
<a href='#' class='bold'><img src='" + settings.media_url + "text_bold.png' alt='bold' title='Bold' /></a>\
<a href='#' class='italic'><img src='" + settings.media_url + "text_italic.png' alt='italic' title='Italicize' /></a>\
</p>\
<p>\
<a href='#' class='unorderedlist'><img src='" + settings.media_url + "text_list_bullets.png' alt='unordered list' title='Unordered List' /></a>\
<a href='#' class='link'><img src='" + settings.media_url + "link.png' alt='link' title='Hyperlink' /></a>\
<a href='#' class='image'><img src='" + settings.media_url + "image.png' alt='image' title='Image' /></a>\
<a href='#' class='disable'><img src='" + settings.media_url + "code.png' alt='close rte' title='View Code' /></a>\
</p></div></div>");
$('select', tb).change(function () {
var index = this.selectedIndex;
if (index != 0) {
var selected = this.options[index].value;
formatText("formatblock", '<' + selected + '>');
}
});
$('.bold', tb).click(function () {
formatText('bold');
return false;
});
$('.italic', tb).click(function () {
formatText('italic');
return false;
});
$('.unorderedlist', tb).click(function () {
formatText('insertunorderedlist');
return false;
});
$('.link', tb).click(function () {
var p = prompt("URL:");
if (p) formatText('CreateLink', p);
return false;
});
$('.image', tb).click(function () {
var p = prompt("image URL:");
if (p) formatText('InsertImage', p);
return false;
});
$('.disable', tb).click(function () {
disableDesignMode();
var edm = $('Enable design mode');
tb.empty().append(edm);
edm.click(function (e) {
e.preventDefault();
enableDesignMode();
$(this).remove();
});
return false;
});
if (settings.dot_net_button_class != null) {
var cmdBtn = $(iframe).parents('form').find(sel.cmdBtnSelector);
cmdBtn.click(function () {
disableDesignMode(true);
});
} else {
$(iframe).parents('form').submit(function () {
disableDesignMode(true);
});
}
var iframeDoc = $(iframe.contentWindow.document);
var select = $('select', tb)[0];
iframeDoc.mouseup(function () {
setSelectedType(getSelectionElement(), select);
return true;
});
iframeDoc.keyup(function () {
setSelectedType(getSelectionElement(), select);
var body = $('body', iframeDoc);
if (body.scrollTop() > 0) {
var iframe_height = parseInt(iframe.style['height']);
if (isNaN(iframe_height)) {
iframe_height = 0;
}
var h = Math.min(opts.max_height, iframe_height + body.scrollTop()) + 'px';
iframe.style['height'] = h;
}
return true;
});
return tb;
}
function formatText(command, option) {
iframe.contentWindow.focus();
try {
iframe.contentWindow.document.execCommand(command, false, option);
} catch (e) {
console.log(e)
}
iframe.contentWindow.focus();
}
function setSelectedType(node, select) {
while (node.parentNode) {
var nName = node.nodeName.toLowerCase();
for (var i = 0; i < select.options.length; i++) {
if (nName == select.options[i].value) {
select.selectedIndex = i;
return true;
}
}
node = node.parentNode;
}
select.selectedIndex = 0;
return true;
}
function getSelectionElement() {
if (iframe.contentWindow.document.selection) {
selection = iframe.contentWindow.document.selection;
range = selection.createRange();
try {
node = range.parentElement();
} catch (e) {
return false;
}
} else {
try {
selection = iframe.contentWindow.getSelection();
range = selection.getRangeAt(0);
} catch (e) {
return false;
}
node = range.commonAncestorContainer;
}
return node;
}
enableDesignMode();
});
};
}
})(jQuery);
The contentEditable DOM attribute is used make the document or a section of it editable. This doesn't work with a <textarea>; more often an <iframe> is created and the document inside of it used as an editor.
Mark Pilgrim has an article explaining it on the blog of the Web Hypertext Application Technology Working Group. It includes an example page showing some of the features in use.
Related
i have this javascript that I called cookiebar.js, it shows a sticky bar message for cookies, (source code)
(function (context) {
"use strict";
var win = context,
doc = win.document;
var global_instance_name = "cbinstance";
function contentLoaded(win, fn) {
var done = false,
top = true,
doc = win.document,
root = doc.documentElement,
add = doc.addEventListener ? "addEventListener" : "attachEvent",
rem = doc.addEventListener ? "removeEventListener" : "detachEvent",
pre = doc.addEventListener ? "" : "on",
init = function (e) {
if (e.type == "readystatechange" && doc.readyState != "complete") return;
(e.type == "load" ? win : doc)[rem](pre + e.type, init, false);
if (!done && (done = true)) fn.call(win, e.type || e);
},
poll = function () {
try {
root.doScroll("left");
} catch (e) {
setTimeout(poll, 50);
return;
}
init("poll");
};
if (doc.readyState == "complete") fn.call(win, "lazy");
else {
if (doc.createEventObject && root.doScroll) {
try {
top = !win.frameElement;
} catch (e) {}
if (top) poll();
}
doc[add](pre + "DOMContentLoaded", init, false);
doc[add](pre + "readystatechange", init, false);
win[add](pre + "load", init, false);
}
}
var Cookies = {
get: function (key) {
return decodeURIComponent(doc.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(key).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
},
set: function (key, val, end, path, domain, secure) {
if (!key || /^(?:expires|max\-age|path|domain|secure)$/i.test(key)) {
return false;
}
var expires = "";
if (end) {
switch (end.constructor) {
case Number:
expires = end === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + end;
break;
case String:
expires = "; expires=" + end;
break;
case Date:
expires = "; expires=" + end.toUTCString();
break;
}
}
doc.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(val) + expires + (domain ? "; domain=" + domain : "") + (path ? "; path=" + path : "") + (secure ? "; secure" : "");
return true;
},
has: function (key) {
return new RegExp("(?:^|;\\s*)" + encodeURIComponent(key).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=").test(doc.cookie);
},
remove: function (key, path, domain) {
if (!key || !this.has(key)) {
return false;
}
doc.cookie = encodeURIComponent(key) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (domain ? "; domain=" + domain : "") + (path ? "; path=" + path : "");
return true;
},
};
var Utils = {
merge: function () {
var obj = {},
i = 0,
al = arguments.length,
key;
if (0 === al) {
return obj;
}
for (; i < al; i++) {
for (key in arguments[i]) {
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
obj[key] = arguments[i][key];
}
}
}
return obj;
},
str2bool: function (str) {
str = "" + str;
switch (str.toLowerCase()) {
case "false":
case "no":
case "0":
case "":
return false;
default:
return true;
}
},
fade_in: function (el) {
if (el.style.opacity < 1) {
el.style.opacity = (parseFloat(el.style.opacity) + 0.05).toFixed(2);
win.setTimeout(function () {
Utils.fade_in(el);
}, 50);
}
},
get_data_attribs: function (script) {
var data = {};
if (Object.prototype.hasOwnProperty.call(script, "dataset")) {
data = script.dataset;
} else {
var attribs = script.attributes;
var key;
for (key in attribs) {
if (Object.prototype.hasOwnProperty.call(attribs, key)) {
var attr = attribs[key];
if (/^data-/.test(attr.name)) {
var camelized = Utils.camelize(attr.name.substr(5));
data[camelized] = attr.value;
}
}
}
}
return data;
},
normalize_keys: function (options_object) {
var camelized = {};
for (var key in options_object) {
if (Object.prototype.hasOwnProperty.call(options_object, key)) {
var camelized_key = Utils.camelize(key);
camelized[camelized_key] = options_object[camelized_key] ? options_object[camelized_key] : options_object[key];
}
}
return camelized;
},
camelize: function (str) {
var separator = "-",
match = str.indexOf(separator);
while (match != -1) {
var last = match === str.length - 1,
next = last ? "" : str[match + 1],
upnext = next.toUpperCase(),
sep_substr = last ? separator : separator + next;
str = str.replace(sep_substr, upnext);
match = str.indexOf(separator);
}
return str;
},
find_script_by_id: function (id) {
var scripts = doc.getElementsByTagName("script");
for (var i = 0, l = scripts.length; i < l; i++) {
if (id === scripts[i].id) {
return scripts[i];
}
}
return null;
},
};
var script_el_invoker = Utils.find_script_by_id("cookiebanner");
var Cookiebanner = (context.Cookiebanner = function (opts) {
this.init(opts);
});
Cookiebanner.prototype = {
cookiejar: Cookies,
init: function (opts) {
this.inserted = false;
this.closed = false;
this.test_mode = false;
var default_text = "This site uses cookies.";
var default_link = "Detail";
this.default_options = {
cookie: "cookiebanner-accepted",
closeText: "✖",
cookiePath: "/",
debug: false,
expires: Infinity,
zindex: 255,
mask: false,
maskOpacity: 0.5,
maskBackground: "#000",
height: "auto",
minHeight: "21px",
bg: "#000",
fg: "#ddd",
link: "#aaa",
position: "bottom",
message: default_text,
linkmsg: default_link,
moreinfo: "http://www.examplesite123.com/cookie-policy/",
effect: null,
fontSize: "14px",
fontFamily: "arial, sans-serif",
instance: global_instance_name,
textAlign: "center",
acceptOnScroll: true,
};
this.options = this.default_options;
this.script_el = script_el_invoker;
if (this.script_el) {
var data_options = Utils.get_data_attribs(this.script_el);
this.options = Utils.merge(this.options, data_options);
}
if (opts) {
opts = Utils.normalize_keys(opts);
this.options = Utils.merge(this.options, opts);
}
global_instance_name = this.options.instance;
this.options.zindex = parseInt(this.options.zindex, 10);
this.options.mask = Utils.str2bool(this.options.mask);
if ("string" === typeof this.options.expires) {
if ("function" === typeof context[this.options.expires]) {
this.options.expires = context[this.options.expires];
}
}
if ("function" === typeof this.options.expires) {
this.options.expires = this.options.expires();
}
if (this.script_el) {
this.run();
}
},
log: function () {
if ("undefined" !== typeof console) {
console.log.apply(console, arguments);
}
},
run: function () {
if (!this.agreed()) {
var self = this;
contentLoaded(win, function () {
self.insert();
});
}
},
build_viewport_mask: function () {
var mask = null;
if (true === this.options.mask) {
var mask_opacity = this.options.maskOpacity;
var bg = this.options.maskBackground;
var mask_markup =
'<div id="cookiebanner-mask" style="' +
"position:fixed;top:0;left:0;width:100%;height:100%;" +
"background:" +
bg +
";zoom:1;filter:alpha(opacity=" +
mask_opacity * 100 +
");opacity:" +
mask_opacity +
";" +
"z-index:" +
this.options.zindex +
';"></div>';
var el = doc.createElement("div");
el.innerHTML = mask_markup;
mask = el.firstChild;
}
return mask;
},
agree: function () {
this.cookiejar.set(this.options.cookie, 1, this.options.expires, this.options.cookiePath);
return true;
},
agreed: function () {
return this.cookiejar.has(this.options.cookie);
},
close: function () {
if (this.inserted) {
if (!this.closed) {
if (this.element) {
this.element.parentNode.removeChild(this.element);
}
if (this.element_mask) {
this.element_mask.parentNode.removeChild(this.element_mask);
}
this.closed = true;
}
}
return this.closed;
},
agree_and_close: function () {
this.agree();
return this.close();
},
cleanup: function () {
this.close();
return this.unload();
},
unload: function () {
if (this.script_el) {
this.script_el.parentNode.removeChild(this.script_el);
}
context[global_instance_name] = undefined;
return true;
},
insert: function () {
this.element_mask = this.build_viewport_mask();
var zidx = this.options.zindex;
if (this.element_mask) {
zidx += 1;
}
var el = doc.createElement("div");
el.className = "cookiebanner";
el.style.position = "fixed";
el.style.left = 0;
el.style.right = 0;
el.style.height = this.options.height;
el.style.minHeight = this.options.minHeight;
el.style.zIndex = zidx;
el.style.background = this.options.bg;
el.style.color = this.options.fg;
el.style.lineHeight = el.style.minHeight;
el.style.padding = "5px 16px";
el.style.fontFamily = this.options.fontFamily;
el.style.fontSize = this.options.fontSize;
el.style.textAlign = this.options.textAlign;
if ("top" === this.options.position) {
el.style.top = 0;
} else {
el.style.bottom = 0;
}
el.innerHTML = '<div class="cookiebanner-close" style="float:right;padding-left:5px;">' + this.options.closeText + "</div>" + "<span>" + this.options.message + " <a>" + this.options.linkmsg + "</a></span>";
this.element = el;
var el_a = el.getElementsByTagName("a")[0];
el_a.href = this.options.moreinfo;
el_a.target = "_blank";
el_a.style.textDecoration = "none";
el_a.style.color = this.options.link;
var el_x = el.getElementsByTagName("div")[0];
el_x.style.cursor = "pointer";
function on(el, ev, fn) {
var add = el.addEventListener ? "addEventListener" : "attachEvent",
pre = el.addEventListener ? "" : "on";
el[add](pre + ev, fn, false);
}
var self = this;
on(el_x, "click", function () {
self.agree_and_close();
});
if (this.element_mask) {
on(this.element_mask, "click", function () {
self.agree_and_close();
});
doc.body.appendChild(this.element_mask);
}
if (this.options.acceptOnScroll) {
on(window, "scroll", function () {
self.agree_and_close();
});
}
doc.body.appendChild(this.element);
this.inserted = true;
if ("fade" === this.options.effect) {
this.element.style.opacity = 0;
Utils.fade_in(this.element);
} else {
this.element.style.opacity = 1;
}
},
};
if (script_el_invoker) {
if (!context[global_instance_name]) {
context[global_instance_name] = new Cookiebanner();
}
}
})(window);
I load it in this way in functions.php in Wordpress:
function wpb_hook_javascript() {
?>
<script defer type="text/javascript" id="cookiebanner" src="https://www.examplesite123.com/cookiebar.js"></script>
<?php
}
add_action('wp_head', 'wpb_hook_javascript');
It works fine.
Now i duplicate the same javascript code and i called it stickybar.js. I add some modifications, also change class with name "stickybar":
The code of the stickybar.js is here (i pasted it in jsfiddle because there is too much text for stackoverflow)
Then i show this second bar (stickybar.js) only on mobile device and after 8 second with this CSS:
.stickybar { display: none; }
#media only screen and (max-device-width:480px) {
.stickybar {
display: block;
animation: cssAnimation 0s 8s forwards;
visibility: hidden;
}
#keyframes cssAnimation {
to { visibility: visible; }
}
}
I load it in Wordpress with this code in functions.php:
function wpb_hook_javascript() {
?>
<script defer type="text/javascript" id="cookiebanner" src="https://www.examplesite123.com/stickybar.js"></script>
<?php
}
add_action('wp_head', 'wpb_hook_javascript');
It works fine.
If i load one by one of this codes, they work fine.
The problem is that when i load the two scripts together in this way in functions.php, only the first works:
function wpb_hook_javascript() {
?>
<script defer type="text/javascript" id="cookiebanner" src="https://www.examplesite123.com/cookiebar.js"></script>
<script defer type="text/javascript" id="cookiebanner" src="https://www.examplesite123.com/stickybar.js"></script>
<?php
}
add_action('wp_head', 'wpb_hook_javascript');
How can i load the two scripts together?
The comment thread on this question is semantically correct, you can only have one instance of each html id attribute, they must be unique, and your find_script_by_id methods are both searching for the same thing.
However, you're doing what's generally called "baking in" the scripts into your header which is at best, a faux pas, at least as far as WordPress is concerned. Properly Enqueueing Scripts (and styles) is very easy in WordPress, and your future self, web clients, and other people who look at your code will thank you for doing it.
It's not unlike how you're "baking in" the scripts now:
function wpb_hook_javascript() {
?>
<script defer type="text/javascript" id="cookiebanner" src="https://www.examplesite123.com/cookiebar.js"></script>
<script defer type="text/javascript" id="cookiebanner" src="https://www.examplesite123.com/stickybar.js"></script>
<?php
}
add_action('wp_head', 'wpb_hook_javascript');
But with a few things changed:
function enqueue_my_scripts(){
wp_enqueue_script( 'cookie-bar', 'https://www.examplesite123.com/cookiebar.js', array(), '1.0', true );
wp_enqueue_script( 'sticky-bar', 'https://www.examplesite123.com/stickybar.js', array(), '1.0', true );
}
add_action( 'wp_enqueue_scripts', 'enqueue_my_scripts' );
Namely, it uses the wp_enqueue_script() function on the wp_enqueue_scripts hook. This lets you defer the script to the footer, load in the header, add version numbers to prevent caching issues, add dependencies, allows you to dynamically add/remove them programatically,gives them unique ID's based on the handle, and much more. (You do still need to update your find_script_by_id functions to use these new handles instead cookie-bar, sticky-bar, change the global_instance_name, etc. (more on that in a second)
With that said, if the .js files are on your server, you'll want to use site_url(), plugins_url(), get_stylesheet_directory_uri(), or similar functions to grab the URL of the file instead of typing it out. If you're using a remote resource, don't worry about it, but if they're on you're site, you should swap out the baked in version for that so you don't have issues if you ever move your site, and it allows you easier methods to edit the version to prevent caching problems if you change them.
Back to your variables, you may also want to replace your find_script_by_id type functions with document.currentScript instead, to allow them to be more abstract and not rely on typo/duplicate prone element IDs, and instead reference the currently running <script> tag.
Hi there I'm pretty new to using functions and onclick actions to call javascript so I could do with some help. Basically, I've installed a plugin on WordPress which adds a button to the page in the form of a widget and once clicked it starts a script. However, I don't like their button so I'm trying to code my own but I want it to start the script like there's.
Here's the script code:
/*
* Timely BookButton plugin
* Example usage:
* var button = new timelyButton('doedayspa');
*
* Booking process can be kicked off manually by calling the start method on the button instance e.g.
* button.start();
*
*/
// Need this for legacy support of older versions of the BookingButton
var timelyButton;
(function () {
"use strict";
var context = window;
var mobile = {
Android: function () {
return navigator.userAgent.match(/Android/i) ? true : false;
},
BlackBerry: function () {
return navigator.userAgent.match(/BlackBerry/i) ? true : false;
},
iOS: function () {
return navigator.userAgent.match(/iPhone|iPod/i) ? true : false;
},
Windows: function () {
return navigator.userAgent.match(/IEMobile/i) ? true : false;
},
any: function () {
return (mobile.Android() || mobile.BlackBerry() || mobile.iOS() || mobile.Windows());
}
};
timelyButton = function (id, opts) {
var options = opts || {};
var businessId = id;
var resellerCode = options.reseller || resellerCode || '';
var productId = options.product || productId || '';
var categoryId = options.category || categoryId || '';
var staffId = options.staff || staffId || '';
var locationId = options.location || locationId || '';
var giftVoucherId = options.giftVoucherId || giftVoucherId || '';
var isPurchaseButton = options.isPurchaseButton != null ? options.isPurchaseButton : false; // default not a purchase
var dontCreateButton = !!options.dontCreateButton;
window.timelyBookFrame = {};
var XD;
var style = options.style || 'light';
var buttonId = options.buttonId || false;
var bookButton;
var scriptSource = (function() {
var script = document.getElementById('timelyScript');
if (script.getAttribute.length !== undefined) {
return script.src;
}
return script.getAttribute('src', -1);
}());
var isOwnImage = !!options.imgSrc;
var imgButtonType = isPurchaseButton ? "purchase-buttons" : "book-buttons";
var imgSrc = options.imgSrc || getDomain() + '/images/' + imgButtonType + '/button_' + style + '#2x.png';
var hoverSrc = getDomain() + '/images/' + imgButtonType + '/button_' + style + '_hover#2x.png';
var activeSrc = getDomain() + '/images/' + imgButtonType + '/button_' + style + '_active#2x.png';
var locationUrl = (isPurchaseButton ? '/giftvoucher/details/' : '/booking/location/') + businessId;
function init() {
if (dontCreateButton) return true;
if (isOwnImage) {
bookButton = document.createElement('a');
bookButton.href = 'javascript:void(0)';
bookButton.onclick = eventHandler.prototype.Book;
bookButton.innerHTML = '<img src=\'' + imgSrc + '\' border=\'0\' />';
} else {
bookButton = document.createElement('a');
bookButton.style.backgroundImage = "url(" + imgSrc + ")";
bookButton.style.backgroundRepeat = "no-repeat";
bookButton.style.backgroundPosition = "0px 0px";
bookButton.style.backgroundSize = (isPurchaseButton ? "220px" : "162px") + " 40px";
bookButton.style.width = isPurchaseButton ? "220px" : "162px";
bookButton.style.height = "40px";
bookButton.style.display = "inline-block";
bookButton.href = 'javascript:void(0)';
bookButton.onclick = eventHandler.prototype.Book;
bookButton.innerHTML += '<img src="' + hoverSrc + '" style="display:none;" border=\'0\' />';
bookButton.innerHTML += '<img src="' + activeSrc + '" style="display:none;" border=\'0\' />';
bookButton.onmouseenter = function() { this.style.backgroundImage = "url(" + hoverSrc + ")"; };
bookButton.onmouseout = function () { this.style.backgroundImage = "url(" + imgSrc + ")"; };
bookButton.onmousedown = function () { this.style.backgroundImage = "url(" + activeSrc + ")"; };
bookButton.onmouseup = function () { this.style.backgroundImage = "url(" + hoverSrc + ")"; };
}
var insertionPoint = findInsertionPoint(buttonId);
insertAfter(bookButton, insertionPoint);
}
function findInsertionPoint(buttonId) {
var insertionPoint = false;
if (buttonId) {
insertionPoint = document.getElementById(buttonId);
} else {
if (("currentScript" in document)) {
insertionPoint = document.currentScript;
} else {
var scripts = document.getElementsByTagName('script');
insertionPoint = scripts[scripts.length - 1];
}
}
return insertionPoint;
}
function getDomain() {
return ('https:' == document.location.protocol ? 'https://' : 'http://') + scriptSource.match( /:\/\/(.[^/]+)/ )[1];
}
function startBooking() {
var url = "";
if (resellerCode) {
url += '&reseller=' + resellerCode;
}
if (productId) {
url += '&productId=' + productId;
}
if (categoryId) {
url += '&categoryId=' + categoryId;
}
if (staffId) {
url += '&staffId=' + staffId;
}
if (locationId) {
url += '&locationId=' + locationId;
}
if (giftVoucherId) {
url += '&giftVoucherId=' + giftVoucherId;
}
if (window.innerWidth < 768 || mobile.any()) {
url = getDomain() + locationUrl + "?mobile=true" + url;
window.location.href = url;
return;
}
window.timelyBookFrame = document.createElement('iframe');
window.timelyBookFrame.className = 'timely-book-frame';
window.timelyBookFrame.style.cssText = 'width: 100%; height: 100%; position: fixed; top: 0; left: 0; z-index: 99999999;';
window.timelyBookFrame.setAttribute('frameBorder', 0);
window.timelyBookFrame.setAttribute('allowTransparency', 'true');
url = getDomain() + (isPurchaseButton ? '/giftvoucher' : '/booking') + '/overlay/' + businessId + '?x' + url;
url += '#' + encodeURIComponent(document.location.href);
window.timelyBookFrame.src = url;
window.timelyBookFrame.style.display = 'none';
document.getElementsByTagName('body')[0].appendChild(window.timelyBookFrame);
var element = document.getElementById('timely-lightbox');
if (typeof(element) != 'undefined' && element != null) {
$('#timely-lightbox').fadeOut();
}
}
function insertAfter(f, n) {
var p = n.parentNode;
if (n.nextSibling) {
p.insertBefore(f, n.nextSibling);
} else {
p.appendChild(f);
}
}
function eventHandler() {
// prototype instance
}
eventHandler.prototype.Book = function() {
startBooking();
};
// everything is wrapped in the XD function to reduce namespace collisions
XD = function () {
var interval_id,
last_hash,
cache_bust = 1,
attached_callback,
window = context;
return {
postMessage: function (message, target_url, target) {
if (!target_url) {
return;
}
target = target || parent; // default to parent
if (window['postMessage']) {
// the browser supports window.postMessage, so call it with a targetOrigin
// set appropriately, based on the target_url parameter.
target['postMessage'](message, target_url.replace(/([^:]+:\/\/[^\/]+).*/, '$1'));
} else if (target_url) {
// the browser does not support window.postMessage, so use the window.location.hash fragment hack
target.location = target_url.replace(/#.*$/, '') + '#' + (+new Date) + (cache_bust++) + '&' + message;
}
},
receiveMessage: function (callback, source_origin) {
// browser supports window.postMessage
if (window['postMessage']) {
// bind the callback to the actual event associated with window.postMessage
if (callback) {
attached_callback = function (e) {
if ((typeof source_origin === 'string' && e.origin !== source_origin)
|| (Object.prototype.toString.call(source_origin) === "[object Function]" && source_origin(e.origin) === !1)) {
return !1;
}
callback(e);
};
}
if (window['addEventListener']) {
window[callback ? 'addEventListener' : 'removeEventListener']('message', attached_callback, !1);
} else {
window[callback ? 'attachEvent' : 'detachEvent']('onmessage', attached_callback);
}
} else {
// a polling loop is started & callback is called whenever the location.hash changes
interval_id && clearInterval(interval_id);
interval_id = null;
if (callback) {
interval_id = setInterval(function () {
var hash = document.location.hash,
re = /^#?\d+&/;
if (hash !== last_hash && re.test(hash)) {
last_hash = hash;
callback({ data: hash.replace(re, '') });
}
}, 100);
}
}
}
};
}();
// setup a callback to handle the dispatched MessageEvent. if window.postMessage is supported the passed
// event will have .data, .origin and .source properties. otherwise, it will only have the .data property.
XD.receiveMessage(function (message) {
if (message.data == 'close') {
var element = document.getElementById('timely-lightbox');
if (typeof (element) != 'undefined' && element != null) {
$('#timely-lightbox').show();
}
if (window.timelyBookFrame && window.timelyBookFrame.parentNode) window.timelyBookFrame.parentNode.removeChild(window.timelyBookFrame);
}
if (message.data == 'open' && window.timelyBookFrame) {
window.timelyBookFrame.style.display = 'block';
}
}, getDomain());
init();
// expose the BookButton API
return {
start: function() {
startBooking();
}
};
};
})();
So how can I run this javascript when I click the button?
Any help would be greatly appreciated!
You could create a script tag automatically if the button has been clicked.
document.getElementById('myButton').addEventListener('click', () => {
const script = document.createElement("script");
script.src = 'my-other-file.js';
document.head.appendChild(script);
})
<button id="myButton">Load JS File</button>
I am trying to work with a simple WYSIWYG editor. JSLint is saying it has "Bad escaping of EOL". Since I am new to javascript I am having a hard time figuring out what it means, since I am working with code found online. Can anyone tell me please what I should be doing instead of ending the line with a slash?
Here is the code in question: http://jsfiddle.net/spadez/KSA5e/9/
/*
* WYSIWYG EDITOR BASED ON JQUERY RTE
*/
// define the rte light plugin
(function ($) {
if (typeof $.fn.rte === "undefined") {
var defaults = {
content_css_url: "rte.css",
dot_net_button_class: null,
max_height: 350
};
$.fn.rte = function (options) {
$.fn.rte.html = function (iframe) {
return iframe.contentWindow.document.getElementsByTagName("body")[0].innerHTML;
};
// build main options before element iteration
var opts = $.extend(defaults, options);
// iterate and construct the RTEs
return this.each(function () {
var textarea = $(this);
var iframe;
var element_id = textarea.attr("id");
// enable design mode
function enableDesignMode() {
var content = textarea.val();
// Mozilla needs this to display caret
if ($.trim(content) === '') {
content = '<br />';
}
// already created? show/hide
if (iframe) {
console.log("already created");
textarea.hide();
$(iframe).contents().find("body").html(content);
$(iframe).show();
$("#toolbar-" + element_id).remove();
textarea.before(toolbar());
return true;
}
// for compatibility reasons, need to be created this way
iframe = document.createElement("iframe");
iframe.frameBorder = 0;
iframe.frameMargin = 0;
iframe.framePadding = 0;
iframe.height = 200;
if (textarea.attr('class')) iframe.className = textarea.attr('class');
if (textarea.attr('id')) iframe.id = element_id;
if (textarea.attr('name')) iframe.title = textarea.attr('name');
textarea.after(iframe);
var css = "";
if (opts.content_css_url) {
css = "<link type='text/css' rel='stylesheet' href='" + opts.content_css_url + "' />";
}
var doc = "<html><head>" + css + "</head><body class='frameBody'>" + content + "</body></html>";
tryEnableDesignMode(doc, function () {
$("#toolbar-" + element_id).remove();
textarea.before(toolbar());
// hide textarea
textarea.hide();
});
}
function tryEnableDesignMode(doc, callback) {
if (!iframe) {
return false;
}
try {
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(doc);
iframe.contentWindow.document.close();
} catch (error) {
console.log(error);
}
if (document.contentEditable) {
iframe.contentWindow.document.designMode = "On";
callback();
return true;
} else if (document.designMode !== null) {
try {
iframe.contentWindow.document.designMode = "on";
callback();
return true;
} catch (error) {
console.log(error);
}
}
setTimeout(function () {
tryEnableDesignMode(doc, callback);
}, 500);
return false;
}
function disableDesignMode(submit) {
var content = $(iframe).contents().find("body").html();
if ($(iframe).is(":visible")) {
textarea.val(content);
}
if (submit !== true) {
textarea.show();
$(iframe).hide();
}
}
// create toolbar and bind events to it's elements
function toolbar() {
var tb = $("<div class='rte-toolbar' id='toolbar-" + element_id + "'><div>\
<p>\
<a href='#' class='bold'>Bold</a>\
<a href='#' class='italic'>Italic</a>\
<a href='#' class='unorderedlist'>List</a>\
</p></div></div>");
$('.bold', tb).click(function () {
formatText('bold');
return false;
});
$('.italic', tb).click(function () {
formatText('italic');
return false;
});
$('.unorderedlist', tb).click(function () {
formatText('insertunorderedlist');
return false;
});
// .NET compatability
if (opts.dot_net_button_class) {
var dot_net_button = $(iframe).parents('form').find(opts.dot_net_button_class);
dot_net_button.click(function () {
disableDesignMode(true);
});
// Regular forms
} else {
$(iframe).parents('form').submit(function () {
disableDesignMode(true);
});
}
var iframeDoc = $(iframe.contentWindow.document);
var select = $('select', tb)[0];
iframeDoc.mouseup(function () {
setSelectedType(getSelectionElement(), select);
return true;
});
iframeDoc.keyup(function () {
setSelectedType(getSelectionElement(), select);
var body = $('body', iframeDoc);
if (body.scrollTop() > 0) {
var iframe_height = parseInt(iframe.style['height']);
if (isNaN(iframe_height)) iframe_height = 0;
var h = Math.min(opts.max_height, iframe_height + body.scrollTop()) + 'px';
iframe.style['height'] = h;
}
return true;
});
return tb;
}
function formatText(command, option) {
iframe.contentWindow.focus();
try {
iframe.contentWindow.document.execCommand(command, false, option);
} catch (e) {
//console.log(e)
}
iframe.contentWindow.focus();
}
function setSelectedType(node, select) {
while (node.parentNode) {
var nName = node.nodeName.toLowerCase();
for (var i = 0; i < select.options.length; i++) {
if (nName == select.options[i].value) {
select.selectedIndex = i;
return true;
}
}
node = node.parentNode;
}
select.selectedIndex = 0;
return true;
}
function getSelectionElement() {
if (iframe.contentWindow.document.selection) {
// IE selections
selection = iframe.contentWindow.document.selection;
range = selection.createRange();
try {
node = range.parentElement();
} catch (e) {
return false;
}
} else {
// Mozilla selections
try {
selection = iframe.contentWindow.getSelection();
range = selection.getRangeAt(0);
} catch (e) {
return false;
}
node = range.commonAncestorContainer;
}
return node;
}
// enable design mode now
enableDesignMode();
}); //return this.each
}; // rte
} // if
$(".rte-zone").rte({});
})(jQuery);
EDIT: For bonus marks there are also two other errors which I haven't been able to squish -
Missing radix parameter
Height is better written in dot notation
JS didn't support end-of-line escaping with \ until ES5 - you can use multiple strings with a + operator instead, i.e.
"string 1" +
"string 2" +
"string 3"
Re: your other questions:
Use parseInt(n, 10) to force base (aka radix) 10, i.e. decimal
Use iframe.style.height instead of iframe.style['height']
You have two options:
1) activate multistr: true as suggested by #csharpfolk. (You can do it at file level by adding /*jshint multistr: true */ or add it in your linter config file (.jshintrc, .eslintrc, etc.)).
2) Replace your multistring as suggested by #Altinak or use an array and join:
["string 1",
"string 2",
"string 3",
].join('')
I have this jQuery-script that gives any image or object with tag data-retina="true" a retina-resolution-image or background-image.
But how do I remove the last part from the script so that this function will apply on every image, not just the one with the data-retina tag?
(function($) {
"use strict";
var retinaReplace = function(element, options) {
this.options = options;
var $el = $(element);
var is_image = $el.is('img');
var normal_url = is_image ? $el.attr('src') : $el.backgroundImageUrl();
var retina_url = this.options.generateUrl($el, normal_url);
$('<img/>').attr('src', retina_url).load(function() {
if (is_image) {
$el.attr('src', $(this).attr('src'));
} else {
$el.backgroundImageUrl($(this).attr('src'));
$el.backgroundSize($(this)[0].width, $(this)[0].height);
}
$el.attr('data-retina', 'complete');
});
}
retinaReplace.prototype = {
constructor: retinaReplace
}
$.fn.retinaReplace = function(option) {
if (getDevicePixelRatio() <= 1) { return this; }
return this.each(function() {
var $this = $(this);
var data = $this.data('retinaReplace');
var options = $.extend({}, $.fn.retinaReplace.defaults, $this.data(), typeof option == 'object' && option);
if (!data) { $this.data('retinaReplace', (data = new retinaReplace(this, options))); }
if (typeof option == 'string') { data[option](); }
});
}
$.fn.retinaReplace.defaults = {
suffix: '-x2',
generateUrl: function(element, url) {
var dot_index = url.lastIndexOf('.');
var extension = url.substr(dot_index + 1);
var file = url.substr(0, dot_index);
return file + this.suffix + '.' + extension;
}
}
$.fn.retinaReplace.Constructor = retinaReplace;
var getDevicePixelRatio = function() {
if (window.devicePixelRatio === undefined) { return 1; }
return window.devicePixelRatio;
}
$.fn.backgroundImageUrl = function(url) {
return url ? this.each(function () {
$(this).css("background-image", 'url("' + url + '")')
}) : $(this).css("background-image").replace(/url\(|\)|"|'/g, "");
}
$.fn.backgroundSize = function(retinaWidth, retinaHeight) {
var sizeValue = Math.floor(retinaWidth/2) + 'px ' + Math.floor(retinaHeight/2) + 'px';
$(this).css("background-size", sizeValue);
$(this).css("-webkit-background-size", sizeValue);
}
$(function(){
$("[data-retina='true']").retinaReplace();
});
})(window.jQuery);
how do I remove the last part from the script so that this function will apply on every image, not just the once with the data-retina-tag?
Change:
$(function(){
$("[data-retina='true']").retinaReplace();
});
To:
$(function(){
$("img").retinaReplace();
});
I have got following jquery tags plugin.
I want to to restrict maxmimum 5 tags, so that user can not enter more than 5 words (separated by spaces).
Can someone please help me doing it?
Thanks.. Following is original plugin code:
(function($) {
var delimiter = new Array();
jQuery.fn.addTag = function(value,options) {
var options = jQuery.extend({focus:false},options);
this.each(function() {
id = $(this).attr('id');
var tagslist = $(this).val().split(delimiter[id]);
if (tagslist[0] == '') {
tagslist = new Array();
}
value = jQuery.trim(value);
if (value !='') {
$('<span class="tag">'+value + ' x</span>').insertBefore('#'+id+'_addTag');
tagslist.push(value);
$('#'+id+'_tag').val('');
if (options.focus) {
$('#'+id+'_tag').focus();
} else {
$('#'+id+'_tag').blur();
}
}
jQuery.fn.tagsInput.updateTagsField(this,tagslist);
});
return false;
};
jQuery.fn.removeTag = function(value) {
this.each(function() {
id = $(this).attr('id');
var old = $(this).val().split(delimiter[id]);
$('#'+id+'_tagsinput .tag').remove();
str = '';
for (i=0; i< old.length; i++) {
if (escape(old[i])!=value) {
str = str + delimiter[id] +old[i];
}
}
jQuery.fn.tagsInput.importTags(this,str);
});
return false;
};
jQuery.fn.tagsInput = function(options) {
var settings = jQuery.extend({defaultText:'add a tag',width:'300px',height:'100px','hide':true,'delimiter':',',autocomplete:{selectFirst:false}},options);
this.each(function() {
if (settings.hide) {
$(this).hide();
}
id = $(this).attr('id')
data = jQuery.extend({
pid:id,
real_input: '#'+id,
holder: '#'+id+'_tagsinput',
input_wrapper: '#'+id+'_addTag',
fake_input: '#'+id+'_tag',
},settings);
delimiter[id] = data.delimiter;
$('<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag"><input id="'+id+'_tag" value="" default="'+settings.defaultText+'" /></div><div class="tags_clear"></div></div>').insertAfter(this);
$(data.holder).css('width',settings.width);
$(data.holder).css('height',settings.height);
if ($(data.real_input).val()!='') {
jQuery.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
} else {
$(data.fake_input).val($(data.fake_input).attr('default'));
$(data.fake_input).css('color','#666666');
}
$(data.holder).bind('click',data,function(event) {
$(event.data.fake_input).focus();
});
// if user types a comma, create a new tag
$(data.fake_input).bind('keypress',data,function(event) {
if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13) {
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true});
return false;
}
});
$(data.fake_input).bind('focus',data,function(event) {
if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('default')) {
$(event.data.fake_input).val('');
}
$(event.data.fake_input).css('color','#000000');
});
if (settings.autocomplete_url != undefined) {
$(data.fake_input).autocomplete(settings.autocomplete_url,settings.autocomplete).bind('result',data,function(event,data,formatted) {
if (data) {
d = data + "";
$(event.data.real_input).addTag(d,{focus:true});
}
});;
$(data.fake_input).bind('blur',data,function(event) {
if ($(event.data.fake_input).val() != $(event.data.fake_input).attr('default')) {
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:false});
}
$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
$(event.data.fake_input).css('color','#666666');
return false;
});
} else {
// if a user tabs out of the field, create a new tag
// this is only available if autocomplete is not used.
$(data.fake_input).bind('blur',data,function(event) {
var d = $(this).attr('default');
if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
event.preventDefault();
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true});
} else {
$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
$(event.data.fake_input).css('color','#666666');
}
return false;
});
}
$(data.fake_input).blur();
});
return this;
};
jQuery.fn.tagsInput.updateTagsField = function(obj,tagslist) {
id = $(obj).attr('id');
$(obj).val(tagslist.join(delimiter[id]));
};
jQuery.fn.tagsInput.importTags = function(obj,val) {
$(obj).val('');
id = $(obj).attr('id');
var tags = val.split(delimiter[id]);
for (i=0; i<tags.length; i++) {
$(obj).addTag(tags[i],{focus:false});
}
};
})(jQuery);
best way is to count the number of "tag" classes already added, and then you can handle it differently, for example you can prevent showing the "add a tag" input once 5 tags inserted by defining maxTags and updating jQuery.fn.addTag and jQuery.fn.removeTag :
/*
jQuery Tags Input Plugin 1.0
Copyright (c) 2010 XOXCO, Inc
Documentation for this plugin lives here:
http://xoxco.com/clickable/jquery-tags-input
Licensed under the MIT license:
http://www.opensource.org/licenses/mit-license.php
ben#xoxco.com
*/
(function($) {
var delimiter = new Array();
var maxTags = 5;
jQuery.fn.addTag = function(value,options) {
var options = jQuery.extend({focus:false},options);
this.each(function() {
id = $(this).attr('id');
var tagslist = $(this).val().split(delimiter[id]);
if (tagslist[0] == '') {
tagslist = new Array();
}
value = jQuery.trim(value);
if (value !='') {
$('<span class="tag">'+value + ' x</span>').insertBefore('#'+id+'_addTag');
tagslist.push(value);
$('#'+id+'_tag').val('');
if (options.focus) {
$('#'+id+'_tag').focus();
} else {
$('#'+id+'_tag').blur();
}
}
jQuery.fn.tagsInput.updateTagsField(this,tagslist);
});
if($(".tag").length>maxTags-1){$('#'+id+'_addTag').hide()}
return false;
};
jQuery.fn.removeTag = function(value) {
this.each(function() {
id = $(this).attr('id');
var old = $(this).val().split(delimiter[id]);
$('#'+id+'_tagsinput .tag').remove();
str = '';
for (i=0; i< old.length; i++) {
if (escape(old[i])!=value) {
str = str + delimiter[id] +old[i];
}
}
jQuery.fn.tagsInput.importTags(this,str);
});
if($(".tag").length<maxTags){$('#'+id+'_addTag').show()}
return false;
};
jQuery.fn.tagsInput = function(options) {
var settings = jQuery.extend({defaultText:'add a tag',width:'300px',height:'100px','hide':true,'delimiter':',',autocomplete:{selectFirst:false}},options);
this.each(function() {
if (settings.hide) {
$(this).hide();
}
id = $(this).attr('id')
data = jQuery.extend({
pid:id,
real_input: '#'+id,
holder: '#'+id+'_tagsinput',
input_wrapper: '#'+id+'_addTag',
fake_input: '#'+id+'_tag',
},settings);
delimiter[id] = data.delimiter;
$('<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag"><input id="'+id+'_tag" value="" default="'+settings.defaultText+'" /></div><div class="tags_clear"></div></div>').insertAfter(this);
$(data.holder).css('width',settings.width);
$(data.holder).css('height',settings.height);
if ($(data.real_input).val()!='') {
jQuery.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
} else {
$(data.fake_input).val($(data.fake_input).attr('default'));
$(data.fake_input).css('color','#666666');
}
$(data.holder).bind('click',data,function(event) {
$(event.data.fake_input).focus();
});
// if user types a comma, create a new tag
$(data.fake_input).bind('keypress',data,function(event) {
if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13) {
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true});
return false;
}
});
$(data.fake_input).bind('focus',data,function(event) {
if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('default')) {
$(event.data.fake_input).val('');
}
$(event.data.fake_input).css('color','#000000');
});
if (settings.autocomplete_url != undefined) {
$(data.fake_input).autocomplete(settings.autocomplete_url,settings.autocomplete).bind('result',data,function(event,data,formatted) {
if (data) {
d = data + "";
$(event.data.real_input).addTag(d,{focus:true});
}
});;
$(data.fake_input).bind('blur',data,function(event) {
if ($(event.data.fake_input).val() != $(event.data.fake_input).attr('default')) {
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:false});
}
$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
$(event.data.fake_input).css('color','#666666');
return false;
});
} else {
// if a user tabs out of the field, create a new tag
// this is only available if autocomplete is not used.
$(data.fake_input).bind('blur',data,function(event) {
var d = $(this).attr('default');
if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
event.preventDefault();
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true});
} else {
$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
$(event.data.fake_input).css('color','#666666');
}
return false;
});
}
$(data.fake_input).blur();
});
return this;
};
jQuery.fn.tagsInput.updateTagsField = function(obj,tagslist) {
id = $(obj).attr('id');
$(obj).val(tagslist.join(delimiter[id]));
};
jQuery.fn.tagsInput.importTags = function(obj,val) {
$(obj).val('');
id = $(obj).attr('id');
var tags = val.split(delimiter[id]);
for (i=0; i<tags.length; i++) {
$(obj).addTag(tags[i],{focus:false});
}
};
})(jQuery);
How about adding something like this:
if($('.tag').length>=5){
$('#tags_tag').attr('disabled','true');
}
I put a little more flair into my demo.
Probably the easiest solution is to change line 89 of jquery.tagsinput.js from:
var skipTag = $(tagslist).tagExist(value);
to:
var skipTag = $(tagslist).length > 5 || $(tagslist).tagExist(value);