From e77851ef60219e89083efca3ab9a125f7d4d8cb4 Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Sat, 30 Apr 2016 21:17:19 +0300 Subject: [PATCH] Remove jquery :) --- src/social-likes.js | 463 +++++++++++++++++++++++++------------------- 1 file changed, 268 insertions(+), 195 deletions(-) diff --git a/src/social-likes.js b/src/social-likes.js index 73cf4bb..c107dc1 100644 --- a/src/social-likes.js +++ b/src/social-likes.js @@ -3,24 +3,14 @@ * http://sapegin.github.com/social-likes * * Sharing buttons for Russian and worldwide social networks. + * jQuery removed, only requires simple utilities: GET, onDomReady, http_build_query, addListener, removeListener * - * @requires jQuery * @author Artem Sapegin * @copyright 2014 Artem Sapegin (sapegin.me) * @license MIT */ -/*global define:false, socialLikesButtons:false */ - -(function(factory) { // Try to register as an anonymous AMD module - if (typeof define === 'function' && define.amd) { - define(['jquery'], factory); - } - else { - factory(jQuery); - } -}(function($, undefined) { - +(function() { 'use strict'; var prefix = 'social-likes'; @@ -29,6 +19,43 @@ var protocol = location.protocol === 'https:' ? 'https:' : 'http:'; var isHttps = protocol === 'https:'; + function hasClass(e, cls, remove) + { + var p = -1; + while ((p = e.className.indexOf(cls, p+1)) != -1) + { + if ((!p || /\s/.exec(e.className.charAt(p-1))) && + (p == e.className.length-cls.length || /\s/.exec(e.className.charAt(p+cls.length)))) + { + if (remove) + e.className = e.className.substr(0, p-1)+e.className.substr(p+cls.length); + else + return true; + } + } + return false; + } + function getScript(url, onsuccess, onerror) + { + var node = document.createElement('script'); + node.type = 'text/javascript'; + node.src = url; + node.onreadystatechange = function(node) + { + if (node.readyState == 'complete') + { + var head = document.head || document.getElementsByTagName('head')[0]; + head.appendChild(node); + onsuccess && onsuccess(); + } + else if (node.readyState == 'loaded') + { + node.children; // IE hack + if (node.readyState == 'loading') + onerror && onerror(); + } + }; + } /** * Buttons @@ -83,8 +110,7 @@ var index = options._.length; options._.push(deferred); - $.getScript(makeUrl(jsonUrl, {index: index})) - .fail(deferred.reject); + getScript(makeUrl(jsonUrl, {index: index}), null, function() { deferred.reject() }); }, popupUrl: 'https://vk.com/share.php?url={url}&title={title}', popupWidth: 655, @@ -104,8 +130,7 @@ var index = options._.length; options._.push(deferred); - $.getScript(makeUrl(jsonUrl, {index: index})) - .fail(deferred.reject); + getScript(makeUrl(jsonUrl, {index: index}), null, function() { deferred.reject() }); }, popupUrl: 'https://connect.ok.ru/dk?st.cmd=WidgetSharePreview&service=odnoklassniki&st.shareUrl={url}', popupWidth: 580, @@ -145,34 +170,42 @@ return servicePromises[url]; } else { - var options = $.extend({}, services[service], extraOptions); - var deferred = $.Deferred(); + var options = {}; + for (var i in services[service]) + options[i] = services[service][i]; + for (var i in extraOptions) + options[i] = extraOptions[i]; + var deferred = { resolve: function(v) {}, reject: function() {} }; var jsonUrl = options.counterUrl && makeUrl(options.counterUrl, {url: url}); - if (jsonUrl && $.isFunction(options.counter)) { + if (jsonUrl && typeof options.counter == 'function') options.counter(jsonUrl, deferred); - } - else if (options.counterUrl) { - $.getJSON(jsonUrl) - .done(function(data) { - try { - var number = data; - if ($.isFunction(options.convertNumber)) { + else if (options.counterUrl) + { + GET(jsonUrl, function(r, d) + { + if (!r.responseText) + deferred.reject(); + else + { + var number = d||r.responseText; + try + { + if (typeof options.convertNumber == 'function') number = options.convertNumber(data); - } deferred.resolve(number); } - catch (e) { + catch (e) + { deferred.reject(); } - }) - .fail(deferred.reject); + } + }); } - else { + else deferred.reject(); - } - servicePromises[url] = deferred.promise(); + servicePromises[url] = deferred; return servicePromises[url]; } } @@ -182,23 +215,27 @@ /** * jQuery plugin */ - $.fn.socialLikes = function(options) { - return this.each(function() { - var elem = $(this); - var instance = elem.data(prefix); - if (instance) { - if ($.isPlainObject(options)) { - instance.update(options); - } + window.socialLikes = function(element, options) { + var instance = element['__'+prefix]; + if (instance) { + if (typeof options == 'object') { + instance.update(options); } - else { - instance = new SocialLikes(elem, $.extend({}, $.fn.socialLikes.defaults, options, dataToOptions(elem))); - elem.data(prefix, instance); - } - }); + } + else { + var c = {}, o = dataToOptions(element); + for (var i in window.socialLikes.defaults) + c[i] = window.socialLikes.defaults[i]; + for (var i in options) + c[i] = options[i]; + for (var i in o) + c[i] = o[i]; + instance = new SocialLikes(element, c); + element['__'+prefix] = instance; + } }; - $.fn.socialLikes.defaults = { + window.socialLikes.defaults = { url: window.location.href.replace(window.location.hash, ''), title: document.title, counters: true, @@ -217,39 +254,46 @@ SocialLikes.prototype = { init: function() { - // Add class in case of manual initialization - this.container.addClass(prefix); + var self = this; - this.single = this.container.hasClass(prefix + '_single'); + // Add class in case of manual initialization + if (!hasClass(this.container, prefix)) + this.container.className += ' '+prefix; + + this.single = hasClass(this.container, prefix+'_single'); this.initUserButtons(); this.countersLeft = 0; this.number = 0; - this.container.on('counter.' + prefix, $.proxy(this.updateCounter, this)); - - var buttons = this.container.children(); + this.container['on_counter.' + prefix] = function(e) { return self.updateCounter(e); }; this.makeSingleButton(); this.buttons = []; - buttons.each($.proxy(function(idx, elem) { - var button = new Button($(elem), this.options); + for (var i = 0; i < this.container.children.length; i++) + { + var button = new Button(this.container.children[i], this.options); this.buttons.push(button); - if (button.options.counterUrl) this.countersLeft++; - }, this)); + if (button.options.counterUrl) + this.countersLeft++; + } - if (this.options.counters) { - this.timer = setTimeout($.proxy(this.appear, this), this.options.wait); - this.timeout = setTimeout($.proxy(this.ready, this, true), this.options.timeout); + if (this.options.counters) + { + this.timer = setTimeout(function() { self.appear() }, this.options.wait); + this.timeout = setTimeout(function() { self.ready() }, this.options.timeout); } - else { + else this.appear(); - } }, initUserButtons: function() { if (!this.userButtonInited && window.socialLikesButtons) { - $.extend(true, services, socialLikesButtons); + for (var i in socialLikesButtons) { + services[i] = services[i] || {}; + for (var j in socialLikesButtons[i]) + services[i][j] = socialLikesButtons[i][j]; + } } this.userButtonInited = true; }, @@ -257,41 +301,39 @@ if (!this.single) return; var container = this.container; - container.addClass(prefix + '_vertical'); - container.wrap($('
', {'class': prefix + '_single-w'})); - container.wrapInner($('
', {'class': prefix + '__single-container'})); - var wrapper = container.parent(); + container.className += ' ' + prefix + '_vertical'; + var wrapper = document.createElement('div'); + wrapper.className = prefix + '_single-w'; + container.parentNode.insertBefore(wrapper, container); + wrapper.appendChild(container); + var d = document.createElement('div'); + d.className = prefix + '__single-container'; + while (container.firstChild) + d.appendChild(container.firstChild); + container.appendChild(d); // Widget - var widget = $('
', { - 'class': getElementClassNames('widget', 'single') - }); - var button = $(template( - '
' + - '' + - '{title}' + - '
', - { - buttonCls: getElementClassNames('button', 'single'), - iconCls: getElementClassNames('icon', 'single'), - title: this.options.singleTitle - } - )); - widget.append(button); - wrapper.append(widget); + var widget = document.createElement('div'); + widget.className = getElementClassNames('widget', 'single'); + widget.innerHTML = '
'+ + this.options.singleTitle+'
'; + wrapper.appendChild(widget); - widget.on('click', function() { - var activeClass = prefix + '__widget_active'; - widget.toggleClass(activeClass); - if (widget.hasClass(activeClass)) { - container.css({left: -(container.width()-widget.width())/2, top: -container.height()}); + addListener(widget, 'click', function() { + var activeClass = prefix+'__widget_active'; + if (!hasClass(widget, activeClass)) { + widget.className += ' '+activeClass; + container.style.left = ((widget.offsetWidth-container.offsetWidth)/2)+'px'; + container.style.top = -container.offsetHeight+'px'; showInViewport(container); closeOnClick(container, function() { - widget.removeClass(activeClass); + hasClass(widget, activeClass, true); }); } else { - container.removeClass(openClass); + widget.className = widget.className.substr(0, i); + hasClass(container, openClass, true); } return false; }); @@ -304,13 +346,17 @@ // Reset counters this.number = 0; this.countersLeft = this.buttons.length; - if (this.widget) this.widget.find('.' + prefix + '__counter').remove(); + if (this.widget) + { + var e = this.widget.querySelector('.' + prefix + '__counter'); + if (e) e.parentNode.removeChild(e); + } // Update options - $.extend(this.options, options); - for (var buttonIdx = 0; buttonIdx < this.buttons.length; buttonIdx++) { + for (var i in options) + this.options[i] = options[i]; + for (var buttonIdx = 0; buttonIdx < this.buttons.length; buttonIdx++) this.buttons[buttonIdx].update(options); - } }, updateCounter: function(e, service, number) { number = number || 0; @@ -329,23 +375,23 @@ this.countersLeft--; }, appear: function() { - this.container.addClass(prefix + '_visible'); + this.container.className += ' '+prefix+'_visible'; }, ready: function(silent) { if (this.timeout) { clearTimeout(this.timeout); } - this.container.addClass(prefix + '_ready'); + this.container.className += ' '+prefix+'_ready'; if (!silent) { - this.container.trigger('ready.' + prefix, this.number); + var e = this.container['on_ready.'+prefix]; + if (e) e(this.number); } }, getCounterElem: function() { - var counterElem = this.widget.find('.' + classPrefix + 'counter_single'); + var counterElem = this.widget.querySelector('.' + classPrefix + 'counter_single'); if (!counterElem.length) { - counterElem = $('', { - 'class': getElementClassNames('counter', 'single') - }); + counterElem = document.createElement('span'); + counterElem.className = getElementClassNames('counter', 'single'); this.widget.append(counterElem); } return counterElem; @@ -355,7 +401,9 @@ function Button(widget, options) { this.widget = widget; - this.options = $.extend({}, options); + this.options = {}; + for (var i in options) + this.options[i] = options[i]; this.detectService(); if (this.service) { this.init(); @@ -366,21 +414,24 @@ init: function() { this.detectParams(); this.initHtml(); - setTimeout($.proxy(this.initCounter, this), 0); + var self = this; + setTimeout(function() { self.initCounter() }, 0); }, update: function(options) { - $.extend(this.options, {forceUpdate: false}, options); - this.widget.find('.' + prefix + '__counter').remove(); // Remove old counter + this.options.forceUpdate = false; + for (var i in options) + this.options[i] = options[i]; + var e = this.widget.querySelector('.' + prefix + '__counter'); + if (e) e.parentNode.removeChild(e); // Remove old counter this.initCounter(); }, detectService: function() { - var service = this.widget.data('service'); + var service = this.widget.getAttribute('data-service'); if (!service) { // class="facebook" - var node = this.widget[0]; - var classes = node.classList || node.className.split(' '); + var classes = this.widget.classList || this.widget.className.split(' '); for (var classIdx = 0; classIdx < classes.length; classIdx++) { var cls = classes[classIdx]; if (services[cls]) { @@ -391,72 +442,71 @@ if (!service) return; } this.service = service; - $.extend(this.options, services[service]); + for (var i in services[service]) + this.options[i] = services[service][i]; }, detectParams: function() { - var data = this.widget.data(); - // Custom page counter URL or number - if (data.counter) { - var number = parseInt(data.counter, 10); - if (isNaN(number)) { - this.options.counterUrl = data.counter; - } - else { + var c = this.widget.getAttribute('data-counter'); + if (c) { + var number = parseInt(c, 10); + if (isNaN(number)) + this.options.counterUrl = c; + else this.options.counterNumber = number; - } } // Custom page title - if (data.title) { - this.options.title = data.title; - } + c = this.widget.getAttribute('data-title'); + if (c) + this.options.title = c; // Custom page URL - if (data.url) { - this.options.url = data.url; - } + c = this.widget.getAttribute('data-url'); + if (c) + this.options.url = c; }, initHtml: function() { + var self = this; var options = this.options; var widget = this.widget; // Old initialization HTML - var a = widget.find('a'); - if (a.length) { + var a = widget.querySelector('a'); + if (a) this.cloneDataAttrs(a, widget); - } // Button - var button = $('', { - 'class': this.getElementClassNames('button'), - 'text': widget.text() - }); + var button = document.createElement('span'); + button.className = this.getElementClassNames('button'); + button.innerHTML = widget.innerHTML; if (options.clickUrl) { var url = makeUrl(options.clickUrl, { url: options.url, title: options.title }); - var link = $('', { - href: url - }); + var link = document.createElement('a'); + link.href = url; this.cloneDataAttrs(widget, link); - widget.replaceWith(link); + widget.parentNode.insertBefore(link, widget); + widget.parentNode.removeChild(widget); this.widget = widget = link; } else { - widget.on('click', $.proxy(this.click, this)); + addListener(widget, 'click', function() { self.click() }); } - widget.removeClass(this.service); - widget.addClass(this.getElementClassNames('widget')); + widget.className = widget.className.replace(' '+this.service, '') + ' '+this.getElementClassNames('widget'); // Icon - button.prepend($('', {'class': this.getElementClassNames('icon')})); + var s = document.createElement('span'); + s.className = this.getElementClassNames('icon'); + button.children.length ? button.insertBefore(s, button.firstChild) : button.appendChild(s); - widget.empty().append(button); + widget.innerHTML = ''; + widget.appendChild(button); this.button = button; }, @@ -470,19 +520,17 @@ counterUrl: this.options.counterUrl, forceUpdate: this.options.forceUpdate }; - counters.fetch(this.service, this.options.url, extraOptions) - .always($.proxy(this.updateCounter, this)); + var self = this; + var r = counters.fetch(this.service, this.options.url, extraOptions); + r.reject = r.resolve = function(n) { self.updateCounter(n) }; } } }, cloneDataAttrs: function(source, destination) { - var data = source.data(); - for (var key in data) { - if (data.hasOwnProperty(key)) { - destination.data(key, data[key]); - } - } + for (var i = 0; i < source.attributes.length; i++) + if (source.attributes[i].name.substr(0, 5) == 'data-') + destination.setAttribute(source.attributes[i].name, source.attributes[i].value); }, getElementClassNames: function(elem) { @@ -492,24 +540,23 @@ updateCounter: function(number) { number = parseInt(number, 10) || 0; - var params = { - 'class': this.getElementClassNames('counter'), - 'text': number - }; + var counterElem = document.createElement('span'); if (!number && !this.options.zeroes) { - params['class'] += ' ' + prefix + '__counter_empty'; - params.text = ''; + counterElem.className = this.getElementClassNames('counter') + ' ' + prefix + '__counter_empty'; + } else { + counterElem.innerHTML = number; + counterElem.className = this.getElementClassNames('counter'); } - var counterElem = $('', params); - this.widget.append(counterElem); + this.widget.appendChild(counterElem); - this.widget.trigger('counter.' + prefix, [this.service, number]); + var e = this.widget['on_counter.'+prefix]; + if (e) e([this.service, number]); }, click: function(e) { var options = this.options; var process = true; - if ($.isFunction(options.click)) { + if (typeof options.click == 'function') { process = options.click.call(this, e); } if (process) { @@ -527,10 +574,17 @@ }, addAdditionalParamsToUrl: function(url) { - var params = $.param($.extend(this.widget.data(), this.options.data)); - if ($.isEmptyObject(params)) return url; - var glue = url.indexOf('?') === -1 ? '?' : '&'; - return url + glue + params; + var params = dataToOptions(this.widget); + for (var i in this.options.data) + params[i] = this.options.data[i]; + var s = ''; + for (var i in params) + s += '&'+encodeURIComponent(i)+'='+encodeURIComponent(params[i]); + if (!s) + return url; + if (!url.indexOf('?')) + s = '?'+s.substr(1); + return url + s; }, openPopup: function(url, params) { @@ -544,12 +598,15 @@ 'width=' + params.width + ',height=' + params.height + ',personalbar=0,toolbar=0,scrollbars=1,resizable=1'); if (win) { win.focus(); - this.widget.trigger('popup_opened.' + prefix, [this.service, win]); - var timer = setInterval($.proxy(function() { + var e = this.widget['on_popup_opened.' + prefix]; + if (e) e([this.service, win]); + var self = this; + var timer = setInterval(function() { if (!win.closed) return; clearInterval(timer); - this.widget.trigger('popup_closed.' + prefix, this.service); - }, this), this.options.popupCheckInterval); + var e = self.widget['on_popup_closed.' + prefix]; + if (e) e(self.service); + }, this.options.popupCheckInterval); } else { location.href = url; @@ -562,18 +619,25 @@ * Helpers */ - // Camelize data-attributes - function dataToOptions(elem) { - function upper(m, l) { + // Camelize data-attributes + function dataToOptions(elem, nocamel) + { + function upper(m, l) + { return l.toUpper(); } var options = {}; - var data = elem.data(); - for (var key in data) { - var value = data[key]; - if (value === 'yes') value = true; - else if (value === 'no') value = false; - options[key.replace(/-(\w)/g, upper)] = value; + for (var i = 0; i < elem.attributes.length; i++) + { + var key = elem.attributes[i].name; + if (key.substr(0, 5) == 'data-') + { + key = key.substr(5); + var value = elem.attributes[i].value; + if (value === 'yes') value = true; + else if (value === 'no') value = false; + options[nocamel ? key : key.replace(/-(\w)/g, upper)] = value; + } } return options; } @@ -594,44 +658,53 @@ return cls + ' ' + cls + '_' + mod; } - function closeOnClick(elem, callback) { - function handler(e) { - if ((e.type === 'keydown' && e.which !== 27) || $(e.target).closest(elem).length) return; - elem.removeClass(openClass); - doc.off(events, handler); - if ($.isFunction(callback)) callback(); + function closeOnClick(elem, callback) + { + function handler(e) + { + if (e.type === 'keydown' && e.which !== 27) + return; + for (var i = e; i && i != elem; i = i.parentNode) {} + if (i == elem) + return; + hasClass(elem, openClass, true); + removeListener(document, 'click', handler); + removeListener(document, 'touchstart', handler); + removeListener(document, 'keydown', handler); + callback(); } - var doc = $(document); var events = 'click touchstart keydown'; - doc.on(events, handler); + addListener(document, 'click', handler); + addListener(document, 'touchstart', handler); + addListener(document, 'keydown', handler); } function showInViewport(elem) { var offset = 10; if (document.documentElement.getBoundingClientRect) { - var left = parseInt(elem.css('left'), 10); - var top = parseInt(elem.css('top'), 10); + var left = parseInt(elem.style.left, 10); + var top = parseInt(elem.style.top, 10); var rect = elem[0].getBoundingClientRect(); if (rect.left < offset) - elem.css('left', offset - rect.left + left); + elem.stype.left = (offset - rect.left + left)+'px'; else if (rect.right > window.innerWidth - offset) - elem.css('left', window.innerWidth - rect.right - offset + left); + elem.style.left = (window.innerWidth - rect.right - offset + left)+'px'; if (rect.top < offset) - elem.css('top', offset - rect.top + top); + elem.style.top = (offset - rect.top + top)+'px'; else if (rect.bottom > window.innerHeight - offset) - elem.css('top', window.innerHeight - rect.bottom - offset + top); + elem.style.top = (window.innerHeight - rect.bottom - offset + top)+'px'; } - elem.addClass(openClass); + elem.className += ' '+openClass; } - /** * Auto initialization */ - $(function() { - $('.' + prefix).socialLikes(); + onDomReady(function() { + var es = document.querySelectorAll('.' + prefix); + for (var i = 0; i < es.length; i++) + socialLikes(es[i]); }); - -})); +})();