Lorem ipsum dolor sit amet, consectetuer adipiscing elit
23 |
24 |
25 |
26 |
Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
23 |
24 |
25 |
26 |
Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
26 |
27 |
28 |
29 |
Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.
` Elemente einfügen
24 | 6. Mit den unten beschriebenen `data`-Attributen Aussehen und Funktion konfigurieren
25 |
26 | Die Share-Counts in den Buttons benötigen ein [Backend](#backends) auf ihrem Server.
27 |
28 | Code-Beispiel:
29 |
30 | ```html
31 |
32 |
33 |
34 |
35 |
36 |
37 |
My article
38 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
39 |
40 |
Einfache Buttons:
41 |
42 |
43 |
Fortgeschrittene Optionen:
44 |
45 |
46 |
47 |
48 |
49 |
50 | ```
51 |
52 | ## Shariff mit `npm` einrichten
53 |
54 | Shariff ist auch als Node-Paket verfügbar und kann mit `npm` in ein Projekt eingebunden werden:
55 |
56 | ```sh
57 | $ cd my-project
58 | $ npm install shariff --save
59 | ```
60 |
61 | Dann kann Shariff im eigenen Skript initialisiert und an DOM-Elemente gebunden werden:
62 |
63 | ```js
64 | // my-app.js
65 | var Shariff = require('shariff');
66 | var $ = require('jquery');
67 | var buttonsContainer = $('.some-selector');
68 | new Shariff(buttonsContainer, {
69 | orientation: 'vertical'
70 | });
71 | ```
72 |
73 | ## Demo-Webseite starten
74 |
75 | Nach dem Download von Shariff mit `npm install` die Abhängigkeiten installieren. Anschließend kann mit `npm run dev` ein lokaler Webserver gestartet werden, der eine Seite mit verschiedenen Konfigurations-Beispielen bereitstellt:
76 |
77 | ```sh
78 | $ git clone https://github.com/heiseonline/shariff.git
79 | $ cd shariff
80 | $ npm install
81 | $ npm run dev
82 | ```
83 |
84 | ## Optionen (data-Attribute)
85 |
86 | | Attribut | Beschreibung | Default |
87 | |------------------|--------------|---------|
88 | | `data-backend-url` | Pfad zum Shariff-[Backend](#backends). Der Wert `null` deaktiviert die Anzeige von Share-Counts. | `null` |
89 | | `data-button-style` | Wie die Buttons angezeigt werden. Werte: `standard`, `icon`, `icon-count`. Bei `icon` wird nur das Icon angezeigt, bei `icon-count` werden Icon und Zähler und bei `standard` Icon, Text und Zähler abhängig von der Display-Größe angezeigt. | `standard` |
90 | | `data-flattr-category` | Kategorie für Flattr-Spende. | `null` |
91 | | `data-flattr-user` | Benutzer, der die Flattr-Spende erhält. | `null` |
92 | | `data-info-url` | URL der Infoseite. | `http://ct.de/-2467514` |
93 | | `data-info-display` | Wie die Infoseite angezeigt wird. Werte: `blank`, `popup`, `self`. | `blank` |
94 | | `data-lang` | Lokalisierung auswählen. Verfügbar: `bg`, `cs`, `da`, `de`, `en`, `es`, `fi`, `fr`, `hr`, `hu`, `it`, `ja`, `ko`, `nl`, `no`, `pl`, `pt`, `ro`, `ru`, `sk`, `sl`, `sr`, `sv`, `tr`, `zh` | `de` |
95 | | `data-mail-body` | Wenn `data-mail-url` ein `mailto:`-Link ist, wird dieser Wert als Mail-Body verwendet. Der Mail-Body-Text sollte den Platzhalter `{url}` enthalten. Dieser wird durch die zu teilende URL ersetzt. | siehe `data-url` |
96 | | `data-mail-subject` | Wenn `data-mail-url` ein `mailto:`-Link ist, wird dieser Wert als Mail-Betreff verwendet. | siehe `data-title` |
97 | | `data-mail-url` | Der Link des `mail`-Buttons | `?view=mail` |
98 | | `data-media-url` | Zu teilendes Bild (pinterest) | `null` |
99 | | `data-orientation` | Anordnung der Buttons. Verfügbar: `horizontal`, `vertical` | `horizontal` |
100 | | `data-referrer-track` | Wenn angegeben, wird dieser String an die geteilte URL angehängt. Mit `null` deaktivieren. | `null` |
101 | | `data-services` | Liste der Services, die verwendet werden sollen. Für die Verwendung im `data`-Attribut muss die Angabe Entity-enkodiert werden. Die Reihenfolge wird berücksichtigt. Beispiel: `data-services="["facebook","twitter"]"` Verfügbare Dienste:`buffer`, `clipboard`, `diaspora`, `facebook`, `flattr`, `flipboard`, `info`, `linkedin`, `mail`, `pinterest`, `pocket`, `print`, `qzone`, `reddit`, `stumbleupon`, `telegram`, `tencent-weibo`, `threema`, `tumblr`, `twitter`, `vk`, `weibo`, `whatsapp`, `xing` | `twitter`, `facebook`, `info` |
102 | | `data-services` | Liste der Services, die verwendet werden sollen. Für die Verwendung im `data`-Attribut muss die Angabe Entity-enkodiert werden. Die Reihenfolge wird berücksichtigt. Beispiel: `data-services="["facebook","twitter"]"` Verfügbare Dienste: `twitter`, `facebook`, `linkedin`, `pinterest`, `xing`, `whatsapp`, `mail`, `info`, `tumblr`, `flattr`, `diaspora`, `reddit`, `stumbleupon`, `threema`, `weibo`, `tencent-weibo`, `qzone`, `print`, `telegram`, `vk`, `flipboard`, `pocket`, `buffer` | `twitter`, `facebook`, `info` |
103 | | `data-theme` | Farbschema auswählen. Verfügbar: `standard`, `grey` und `white`. | `standard` |
104 | | `data-title` | Titel der zu teilenden Seite. | Entweder `DC.title`/`DC.creator` oder `` |
105 | | `data-twitter-via` | User von dem der Tweet ursprünglich stammt. | `null` |
106 | | `data-url` | URL, die geteilt werden soll. | Wenn `data-url` nicht gesetzt ist, wird `link[rel="canonical"]`, `meta[property="og:url"]` oder `location.href` verwendet. |
107 |
108 | ## Konstruktor-Argumente
109 |
110 | Alle data-Attribute von oben sind auch als Konstruktor-Argumente in JavaScript verwendbar. Dabei wird das `data-` am Anfang weggestrichen und camelCase statt kebab-case verwendet:
111 |
112 | ```js
113 | var buttonsContainer = $('.some-selector');
114 | new Shariff(buttonsContainer, {
115 | backendUrl: '/my/backend/url',
116 | orientation: 'vertical',
117 | mailUrl: 'mailto:me@example.com',
118 | });
119 | ```
120 |
121 | ## Events
122 |
123 | Beim Klick auf einen Share-Button wird der `shariff-share` Event ausgelöst.
124 |
125 | ```js
126 | $('body').on('shariff-share', function(event) {
127 | var service = event.details;
128 | ...
129 | });
130 | ```
131 |
132 | Der Event kann verwendet werden um die Interaktionen mittels Analyse-Software aufzuzeichnen. Eine saubere Integration mit dem Tracker setzt voraus, dass der Event-Handler erst registriert wird nachdem das Analytics-Script geladen wurde.
133 |
134 | **Beispiel für Piwik:**
135 |
136 | ```js
137 | (function() {
138 | var _my_piwik_onload = function() {
139 | var piwik = this;
140 |
141 | $('body').on('shariff-share', function(event) {
142 | var service = event.detail;
143 | piwik.trackEvent('Sharing', service.name);
144 | });
145 | }
146 |
147 | _paq.push([ _my_piwik_onload ]);
148 | })();
149 | ```
150 |
151 | ## Unterstützte Browser
152 |
153 | Shariff unterstützt folgende Browser:
154 |
155 | - Firefox
156 | - Google Chrome
157 | - Internet Explorer/Edge
158 | - Safari
159 |
160 | Die jeweils aktuell letzten und vorletzten Versionen von Firefox, Google Chrome, Internet Explorer/Edge und Safari werden untersützt.
161 |
162 | ## Unterstützte Services
163 |
164 | Shariff unterstützt folgende Social-Sharing-Services:
165 |
166 | - buffer
167 | - diaspora*
168 | - facebook
169 | - Flattr
170 | - Flipboard
171 | - LinkedIn
172 | - Mail
173 | - Pinterest
174 | - Pocket
175 | - Print
176 | - Qzone
177 | - reddit
178 | - StumbleUpon
179 | - Telegram
180 | - Tencent Weibo
181 | - Threema
182 | - Tumblr
183 | - Twitter
184 | - VK
185 | - Weibo
186 | - WhatsApp
187 | - XING
188 |
189 | Zusätzlich stellt der Service `Info` einen Button zur Anzeige einer Infoseite über die Social-Sharing-Buttons bereit.
190 | Die URL dieser Seite kann mit einer Option festgelegt werden. Standardwert: `http://ct.de/-2467514`, d.h. der c't-Artikel zur Einführung von Shariff.
191 |
192 | ## Backends
193 |
194 | Wenn in den Shariff-Buttons die Share-Counts angezeigt werden sollen, wird das folgende Backend benötigt:
195 |
196 | * [shariff-backend-php](http://github.com/heiseonline/shariff-backend-php)
197 |
198 | Drittanbieter-Backends:
199 |
200 | * [shariff-backend-java](https://github.com/shred/shariff-backend-java)
201 |
202 | Die URL, unter der das Backend erreichbar ist, muss im `data`-Attribut `data-backend-url` angegeben werden. Ein Backend unter der URL `http://example.com/my-shariff-backend/` wird in `data-backend-url` so angegeben: `/my-shariff-backend/`. Den Rest erledigt das Skript.
203 |
204 | ## Drittanbieter-Integrationen
205 |
206 | Bekannte Shariff-Integrationen für Drittanbieter-Systeme:
207 |
208 | * [Contao-Integration](https://github.com/hofff/contao-shariff)
209 | * [Drupal-Modul](https://www.drupal.org/project/shariff)
210 | * [Joomla! 3 Shariff-Plugin](https://github.com/joomla-agency/plg_jooag_shariff)
211 | * [Kirby-CMS Shariff-Plugin](https://github.com/SpicyWeb-de/kirby-plugin-shariff)
212 | * [MediaWiki Extension](https://github.com/vonloxley/Shariff-Mediawiki/)
213 | * [Open Monograph Press-Plugin](https://github.com/langsci/shariff)
214 | * [Pagekit Extension](https://pagekit.com/marketplace/package/spqr/shariff)
215 | * [Serendipity Plugin](https://github.com/s9y/additional_plugins/tree/master/serendipity_event_social)
216 | * [SilverStripe-Modul](https://github.com/andrelohmann/silverstripe-shariff)
217 | * [Symfony ShariffBundle](https://github.com/core23/ShariffBundle)
218 | * [TYPO3-Plugin rx_shariff](http://typo3.org/extensions/repository/view/rx_shariff)
219 | * [Wordpress-Plugin shariff-sharing](https://wordpress.org/plugins/shariff-sharing/)
220 | * [WordPress-Plugin Shariff Wrapper](https://wordpress.org/plugins/shariff/)
221 | * [Xenforo [ITM] ctSSB für Xenforo 1.5](https://github.com/McAtze/-ITM-ctShariffSocialButtons)
222 | * [Xenforo [WMTech] Social Share Privacy Plugin](https://wmtech.net/products/wmtech-social-share-privacy.41/)
223 | * [Magento 2 Shariff Social Share](https://www.jajuma.de/de/jajuma-develop/extensions/shariff-social-share-buttons-extension-fuer-magento-2)
224 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demonstration: Shariff
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
19 |
20 |
21 |
22 |
Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
34 |
35 |
36 |
37 |
A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
50 |
51 |
52 |
53 |
A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
65 |
66 |
67 |
68 |
A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
82 |
83 |
84 |
85 |
A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
100 |
101 |
102 |
103 |
Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
114 |
115 |
116 |
117 |
Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.
118 |
119 |
120 |
121 |
data-backend-url:
/shariff/?foo=bar
122 |
data-orientation:
vertical
123 |
124 |
125 |
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
126 |
129 |
130 |
The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn’t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane.
131 |
132 |
133 |
134 |
135 |
data-backend-url:
/shariff/?foo=bar
136 |
data-orientation:
vertical
137 |
data-button-style:
icon-count
138 |
139 |
140 |
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
141 |
144 |
145 |
The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn’t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane.
146 |
147 |
148 |
149 |
150 |
data-backend-url:
/shariff/?foo=bar
151 |
data-orientation:
vertical
152 |
data-button-style:
icon
153 |
154 |
155 |
Lorem ipsum dolor sit amet, consectetuer adipiscing elit
156 |
159 |
160 |
The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn’t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane.
161 |
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/src/js/dom.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
4 | if (typeof Object.assign !== 'function') {
5 | // jshint maxdepth:4
6 | Object.assign = function (target, varArgs) {
7 | // .length of function is 2
8 | if (target === null) {
9 | // TypeError if undefined or null
10 | throw new TypeError('Cannot convert undefined or null to object')
11 | }
12 |
13 | var to = Object(target)
14 |
15 | for (var index = 1; index < arguments.length; index++) {
16 | var nextSource = arguments[index]
17 |
18 | if (nextSource !== null) {
19 | // Skip over if undefined or null
20 | for (var nextKey in nextSource) {
21 | // Avoid bugs when hasOwnProperty is shadowed
22 | if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
23 | to[nextKey] = nextSource[nextKey]
24 | }
25 | }
26 | }
27 | }
28 | return to
29 | }
30 | }
31 |
32 | /**
33 | * Initialization helper. This method is this module's exported entry point.
34 | * @param {string|Array|Element} selector - css selector, one element, array of nodes or html fragment
35 | * @param {node} [context=document] - context node in which to query
36 | * @returns {DOMQuery} A DOMQuery instance containing the selected set of nodes
37 | */
38 | function dq(selector, context) {
39 | var nodes = []
40 | context = context || document
41 | if (typeof selector === 'function') {
42 | if (
43 | context.attachEvent
44 | ? context.readyState === 'complete'
45 | : context.readyState !== 'loading'
46 | ) {
47 | selector()
48 | } else {
49 | context.addEventListener('DOMContentLoaded', selector)
50 | }
51 | } else if (selector instanceof Element) {
52 | nodes = [selector]
53 | } else if (typeof selector === 'string') {
54 | if (selector[0] === '<') {
55 | nodes = Array.prototype.slice.call(fragment(selector))
56 | } else {
57 | nodes = Array.prototype.slice.call(context.querySelectorAll(selector))
58 | }
59 | } else {
60 | nodes = selector
61 | }
62 | return new DOMQuery(nodes, context)
63 | }
64 |
65 | /**
66 | * Contains a set of DOM nodes and provides methods to manipulate the nodes.
67 | * @constructor
68 | */
69 | function DOMQuery(elements, context) {
70 | this.length = elements.length
71 | this.context = context
72 | var self = this
73 | each(elements, function (i) {
74 | self[i] = this
75 | })
76 | }
77 |
78 | /**
79 | * Iterates through each node and calls the callback in its context.
80 | * @param {eachArrayCallback} callback - A function to be called with a node
81 | * @returns {DOMQuery}
82 | */
83 | DOMQuery.prototype.each = function (callback) {
84 | for (var i = this.length - 1; i >= 0; i--) {
85 | callback.call(this[i], i, this[i])
86 | }
87 | return this
88 | }
89 |
90 | /**
91 | * Empties each node.
92 | * @returns {DOMQuery}
93 | */
94 | DOMQuery.prototype.empty = function () {
95 | return this.each(empty)
96 | }
97 |
98 | /**
99 | * Sets the text content of each node. Returns the text content of the first node.
100 | * @param {string} [text] - The text content to set
101 | * @returns {DOMQuery|string}
102 | */
103 | DOMQuery.prototype.text = function (text) {
104 | if (text === undefined) {
105 | return this[0].textContent
106 | }
107 | return this.each(function () {
108 | this.textContent = text
109 | })
110 | }
111 |
112 | /**
113 | * Sets an attribute on each node. Returns the attribute's value of the first node.
114 | * @param {string} [name] - The attribute's name
115 | * @param {string} [value] - The value to set
116 | * @returns {DOMQuery|string}
117 | */
118 | DOMQuery.prototype.attr = function (name, value) {
119 | if (this.length < 1) {
120 | return null
121 | }
122 | if (value === undefined) {
123 | return this[0].getAttribute(name)
124 | }
125 | return this.each(function () {
126 | this.setAttribute(name, value)
127 | })
128 | }
129 |
130 | /**
131 | * Sets a data attribute on each node. Returns the data attribute's value of the first node.
132 | * Supports deserialization of complex data types as values.
133 | * @param {string} [key] - The attribute's name
134 | * @param {string} [value] - The value to set
135 | * @returns {DOMQuery|string|Object}
136 | */
137 | DOMQuery.prototype.data = function (key, value) {
138 | if (value) {
139 | return this.attr('data-' + key, value)
140 | }
141 | if (key) {
142 | return this.attr('data-' + key)
143 | }
144 | var data = Object.assign({}, this[0].dataset)
145 | each(data, function (k, v) {
146 | data[k] = deserializeValue(v)
147 | })
148 | return data
149 | }
150 |
151 | /**
152 | * Returns a new DOMQuery instance containing all matched nodes in the context
153 | * of the set of nodes.
154 | * @param {string} selector - The CSS selector
155 | * @returns {DOMQuery}
156 | */
157 | DOMQuery.prototype.find = function (selector) {
158 | var matches
159 | // querySelectorAll in the context of each element in the set
160 | matches = map(this, function (el) {
161 | return el.querySelectorAll(selector)
162 | })
163 | // convert NodeList matches into Array
164 | matches = map(matches, function (el) {
165 | return Array.prototype.slice.call(el)
166 | })
167 | // flatten the array
168 | matches = Array.prototype.concat.apply([], matches)
169 | return new DOMQuery(matches)
170 | }
171 |
172 | /**
173 | * Appends nodes to the end of the first node in the set.
174 | * @param {string|Array} html - Nodes to append. May be a string containing HTML.
175 | * @returns {DOMQuery}
176 | */
177 | DOMQuery.prototype.append = function (html) {
178 | if (typeof html === 'string') {
179 | html = fragment(html)
180 | }
181 | append(this[0], html)
182 | return this
183 | }
184 |
185 | /**
186 | * Prepends nodes at the top of the first node in the set.
187 | * @param {string|Array} html - Nodes to append. May be a string containing HTML.
188 | * @returns {DOMQuery}
189 | */
190 | DOMQuery.prototype.prepend = function (html) {
191 | if (typeof html === 'string') {
192 | html = fragment(html)
193 | }
194 | prepend(this[0], html)
195 | return this
196 | }
197 |
198 | /**
199 | * Adds a CSS class name to the nodes in the set.
200 | * @param {string} name - Class name to add
201 | * @returns {DOMQuery}
202 | */
203 | DOMQuery.prototype.addClass = function (names) {
204 | return this.each(function () {
205 | // Workaround: IE only supports a single parameter to classList.add()
206 | names.split(' ').forEach((className) => {
207 | this.classList.add(className)
208 | })
209 | })
210 | }
211 |
212 | /**
213 | * Removes a CSS class name from the nodes in the set.
214 | * @param {string} name - Class name to remove
215 | * @returns {DOMQuery}
216 | */
217 | DOMQuery.prototype.removeClass = function (name) {
218 | return this.each(function () {
219 | this.classList.remove(name)
220 | })
221 | }
222 |
223 | /**
224 | * Delegates an event for a node matching a selector to each element in the set.
225 | * @param {string} event - The event name
226 | * @param {string} selector - The CSS selector
227 | * @param {eventHandler} handler - The event handler function
228 | * @returns {DOMQuery}
229 | */
230 | DOMQuery.prototype.on = function (event, selector, handler) {
231 | return this.each(function () {
232 | delegateEvent(selector, event, handler, this)
233 | })
234 | }
235 |
236 | /**
237 | * Execute all handlers and behaviors attached to the matched elements for the
238 | * given event type.
239 | * @param {string} event - The event name
240 | * @param {object} data - Event data
241 | */
242 | DOMQuery.prototype.trigger = function (event, detail) {
243 | this.each(function () {
244 | var evt = new CustomEvent(event, {
245 | detail: detail,
246 | bubbles: true,
247 | cancelable: true,
248 | })
249 | this.dispatchEvent(evt)
250 | })
251 | }
252 |
253 | /**
254 | * Removes each child of a node.
255 | * @private
256 | */
257 | var empty = function () {
258 | while (this.hasChildNodes()) {
259 | this.removeChild(this.firstChild)
260 | }
261 | }
262 |
263 | /**
264 | * Callback function used for map(array, callback).
265 | *
266 | * @callback mapCallback
267 | * @param {Object} object - An element of the array
268 | * @return {Array}
269 | * @see map
270 | */
271 |
272 | /**
273 | * Runs a callback with each element in an array and returns a new array.
274 | * @param {Array} objects - The array to iterate
275 | * @param {function} callback - The callback function
276 | * @returns {Array}
277 | */
278 | var map = function (objects, callback) {
279 | return Array.prototype.map.call(objects, callback)
280 | }
281 |
282 | /**
283 | * Callback function used for each(array, callback).
284 | * Called in the context of each element in the array.
285 | *
286 | * @callback eachArrayCallback
287 | * @param {number} index - Index of the current array element
288 | * @param {object} value - Element of the array
289 | * @return {Array}
290 | * @see each
291 | */
292 |
293 | /**
294 | * Callback function used for each(object, callback).
295 | * Called in the context of each object property value.
296 | *
297 | * @callback eachObjectCallback
298 | * @param {Object} key - The object's property key
299 | * @param {object} value - The object's property value
300 | * @return {Array}
301 | * @see each
302 | */
303 |
304 | /**
305 | * Runs a callback with each element in an array or key-value pair of an object.
306 | * Returns the original object/array.
307 | * @param {Object} object - The object to itrate
308 | * @param {eachArrayCallback|eachObjectCallback} callback - The callback function
309 | * @returns {Array}
310 | */
311 | var each = function (object, callback) {
312 | if (object instanceof Array) {
313 | for (var i = 0; i < object.length; i++) {
314 | callback.call(object[i], i, object[i])
315 | }
316 | } else if (object instanceof Object) {
317 | for (var prop in object) {
318 | callback.call(object[prop], prop, object[prop], object)
319 | }
320 | }
321 | return object
322 | }
323 |
324 | /**
325 | * Constructs HTML nodes from a string of HTML.
326 | * @param {string} html - String of HTML code
327 | * @returns {Array}
328 | * @private
329 | */
330 | var fragment = function (html) {
331 | var div = document.createElement('div')
332 | div.innerHTML = html
333 | return div.children
334 | }
335 |
336 | /**
337 | * Appends an array of nodes to the end of an HTML element.
338 | * @param {Element} parent - Element to append to
339 | * @param {Array} nodes - Collection of nodes to append
340 | * @private
341 | */
342 | var append = function (parent, nodes) {
343 | for (var i = 0; i < nodes.length; i++) {
344 | parent.appendChild(nodes[i])
345 | }
346 | }
347 |
348 | /**
349 | * Prepends an array of nodes to the top of an HTML element.
350 | * @param {Element} parent - Element to prepend to
351 | * @param {Array} nodes - Collection of nodes to prepend
352 | * @private
353 | */
354 | var prepend = function (parent, nodes) {
355 | for (var i = nodes.length - 1; i >= 0; i--) {
356 | parent.insertBefore(nodes[nodes.length - 1], parent.firstChild)
357 | }
358 | }
359 |
360 | /**
361 | * Returns the closest parent of a node matching a CSS selector.
362 | * @param {HTMLElement} element - Element to append to
363 | * @param {string} selector - CSS selector
364 | * @param {HTMLElement}
365 | * @private
366 | * @see {@link https://gist.github.com/Daniel-Hug/abbded91dd55466e590b}
367 | */
368 | var closest = (function () {
369 | var element = HTMLElement.prototype
370 | var matches =
371 | element.matches ||
372 | element.webkitMatchesSelector ||
373 | element.mozMatchesSelector ||
374 | element.msMatchesSelector
375 |
376 | return function closest(element, selector) {
377 | if (element === null) return
378 | return matches.call(element, selector)
379 | ? element
380 | : closest(element.parentElement, selector)
381 | }
382 | })()
383 |
384 | /**
385 | * An event handler.
386 | *
387 | * @callback eventHandler
388 | * @param {Event} event - The event this handler was triggered for
389 | */
390 |
391 | /**
392 | * Delegates an event for a node matching a selector.
393 | * @param {string} selector - The CSS selector
394 | * @param {string} event - The event name
395 | * @param {eventHandler} handler - The event handler function
396 | * @param {HTMLElement} [scope=document] - Element to add the event listener to
397 | * @private
398 | */
399 | var delegateEvent = function (selector, event, handler, scope) {
400 | ;(scope || document).addEventListener(event, function (event) {
401 | var listeningTarget = closest(event.target, selector)
402 | if (listeningTarget) {
403 | handler.call(listeningTarget, event)
404 | }
405 | })
406 | }
407 |
408 | /**
409 | * Extends properties of all arguments into a single object.
410 | * @param {...Object} objects - Objects to merge
411 | * @param {string} event - The event name
412 | * @param {function} handler - The event handler function
413 | * @returns {Object}
414 | * @see {@link https://gomakethings.com/vanilla-javascript-version-of-jquery-extend/}
415 | */
416 | var extend = function (objects) {
417 | // Variables
418 | var extended = {}
419 | var deep = false
420 | var i = 0
421 | var length = arguments.length
422 |
423 | // Check if a deep merge
424 | if (Object.prototype.toString.call(arguments[0]) === '[object Boolean]') {
425 | deep = arguments[0]
426 | i++
427 | }
428 |
429 | // Merge the object into the extended object
430 | var merge = function (obj) {
431 | for (var prop in obj) {
432 | if (Object.prototype.hasOwnProperty.call(obj, prop)) {
433 | // If deep merge and property is an object, merge properties
434 | if (
435 | deep &&
436 | Object.prototype.toString.call(obj[prop]) === '[object Object]'
437 | ) {
438 | extended[prop] = extend(true, extended[prop], obj[prop])
439 | } else {
440 | extended[prop] = obj[prop]
441 | }
442 | }
443 | }
444 | }
445 |
446 | // Loop through each object and conduct a merge
447 | for (; i < length; i++) {
448 | var obj = arguments[i]
449 | merge(obj)
450 | }
451 |
452 | return extended
453 | }
454 |
455 | /**
456 | * The callback function for getJSON().
457 | *
458 | * @callback xhrCallback
459 | * @param {boolean} success - True on success. False on error.
460 | * @param {Object} data - The parsed data. null if success == false
461 | * @param {XMLHttpRequest} xhr - The request object
462 | * @see getJSON
463 | */
464 |
465 | /**
466 | * Runs an Ajax request against a url and calls the callback function with
467 | * the parsed JSON result.
468 | * @param {string} url - The url to request
469 | * @param {xhrCallback} callback - The callback function
470 | * @returns {XMLHttpRequest}
471 | */
472 | var getJSON = function (url, callback) {
473 | var xhr = new XMLHttpRequest()
474 | xhr.open('GET', url, true)
475 | xhr.setRequestHeader('Content-Type', 'application/json')
476 | xhr.setRequestHeader('Accept', 'application/json')
477 |
478 | xhr.onload = function () {
479 | if (xhr.status >= 200 && xhr.status < 400) {
480 | var data = JSON.parse(xhr.responseText)
481 | callback(data, xhr.status, xhr)
482 | } else {
483 | callback(null, xhr.status, xhr)
484 | }
485 | }
486 |
487 | xhr.onerror = function (e) {
488 | callback(new Error(e), null, xhr)
489 | }
490 |
491 | xhr.send()
492 | }
493 |
494 | /**
495 | * Deserializes JSON values from strings. Used with data attributes.
496 | * @param {string} value - String to parse
497 | * @returns {Object}
498 | * @private
499 | */
500 | var deserializeValue = function (value) {
501 | /* jshint maxcomplexity:7 */
502 | // boolean
503 | if (value === 'true') {
504 | return true
505 | }
506 | if (value === 'false') {
507 | return false
508 | }
509 | // null
510 | if (value === 'null') {
511 | return null
512 | }
513 | // number
514 | if (+value + '' === value) {
515 | return +value
516 | }
517 | // json
518 | if (/^[[{]/.test(value)) {
519 | try {
520 | return JSON.parse(value)
521 | } catch (e) {
522 | return value
523 | }
524 | }
525 | // everything else
526 | return value
527 | }
528 |
529 | dq.extend = extend
530 | dq.map = map
531 | dq.each = each
532 | dq.getJSON = getJSON
533 |
534 | module.exports = dq
535 |
--------------------------------------------------------------------------------