diff --git a/.gitignore b/.gitignore index e3e4232..52608f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ .env +env.docker node_modules/ npm-debug.log npm-debug.log.* public/js/ + +.DS_Store +.idea diff --git a/README.md b/README.md index 55b4c4c..6e5696b 100644 --- a/README.md +++ b/README.md @@ -14,48 +14,7 @@ The PaperBadger widget enables *anyone*, from publishers to individual researche ![Badge Preview](./public/img/badge_preview.jpg) -1. To use the widget on your own site, include a `
` with your custom class in your view file, for example: - `
` - -2. Above the closing `` tag, add - - ```html - - ``` - -3. In your scripts, include your custom values: - * the class name where your widget will appear for the `container-class` key and - * the doi for the paper you are interested in as the `article-doi` key - -```html - - - - - Paper view snippet example | Paper Badger - - - -
- - - - - -``` +You can either use paper-badger-widget.js ([documentation](docs/paper-badger-widget.md)), which supports old browsers or widget.js ([documentation](docs/widget.md)), which is in development and currently only supports evergreen browsers and IE9+. ## Contributing diff --git a/docs/paper-badger-widget.md b/docs/paper-badger-widget.md new file mode 100644 index 0000000..e7d49e5 --- /dev/null +++ b/docs/paper-badger-widget.md @@ -0,0 +1,43 @@ +# paper-badger-widget.js Documentation +1. To use the widget on your own site, include a `
` with your custom class in your view file, for example: + `
` + +2. Above the closing `` tag, add + + ```html + + ``` + +3. In your scripts, include your custom values: + * the class name where your widget will appear for the `container-class` key and + * the doi for the paper you are interested in as the `article-doi` key + +```html + + + + + Paper view snippet example | Paper Badger + + + +
+ + + + + +``` \ No newline at end of file diff --git a/docs/widget.md b/docs/widget.md new file mode 100644 index 0000000..37ebcd5 --- /dev/null +++ b/docs/widget.md @@ -0,0 +1,45 @@ +# widget.js Documentation +This shows what the Paper Badger Widget can do and how to use it. + +## Usage +1. To use the widget on your own site, include a `
` with your custom class in your view file, for example: + `
` + +2. Above the closing `` tag, add + ```html + + ``` + +3. In the load method, include your custom values: + * the element class where your widget will appear for the `containerClass` key and + * either the doi for the paper you are interested in as the `DOI` key or the ORCID for the author you are interesed in as the `ORCID` key + +## Required configuration options + * the `containerClass` key: contains the class of the element which will contain the Paper Badger widget + * either the `DOI` or the `ORCID` key: defines which information the widget will display + +## Optional configuration options + * the `ORCIDWidgetType` key (only in combination with the `ORCID` key): set the key to the value `default` to show names below the badges, set it to `DOI` to show DOIs + * the `loaderText` key: changes the default text shown while the Paper Badger widget is loading + * the `removeClass` key: the widget will remove this class from all elements once it has loaded, if this is a non-empty string + * the `clickCallback` key: a method that gets called when a link under a badge gets clicked. The method is called with an object containing either the DOI or ORCID of the link and the badge taxonomy: + + ``` + { + doi: '10.1186/2047-217X-3-18', // either DOI + orcid: '0000-0001-5207-5061', // or ORCID + taxonomy: 'data_curation' + } + ``` diff --git a/package.json b/package.json index 2edbdcf..3fb05fc 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,10 @@ "watch:js": "npm run build:js -- --watch", "clean": "rm -r public/js/*", "server": "node src/index.js", - "jscs": "jscs -c node_modules/mofo-style/linters/.jscsrc src test webpack.config.js", - "jshint": "jshint -c .jshintrc src webpack.config.js", + "jscs": "jscs -c node_modules/mofo-style/linters/.jscsrc src test webpack.config.js public/widgets/widget.js", + "jshint": "jshint -c .jshintrc src webpack.config.js public/widgets/widget.js", "jshint:test": "jshint -c test/.jshintrc test", - "jsbeautify": "js-beautify --config node_modules/mofo-style/linters/.jsbeautifyrc src/*.js test/*.js webpack.config.js -r -n", + "jsbeautify": "js-beautify --config node_modules/mofo-style/linters/.jsbeautifyrc src/*.js test/*.js webpack.config.js public/widgets/widget.js -r -n", "lint": "npm run jsbeautify && npm run jshint && npm run jshint:test && npm run jscs", "build:production": "npm run build:js -- --optimize-minimize --optimize-dedupe", "start:production": "npm-run-all build:production server", diff --git a/public/widgets/demos/doi-widget.html b/public/widgets/demos/doi-widget.html new file mode 100644 index 0000000..e8d6967 --- /dev/null +++ b/public/widgets/demos/doi-widget.html @@ -0,0 +1,28 @@ + + + + + Paper view snippet example | Paper Badger + + + + +
+
This text is only shown when the API returns badges.
+ + + + \ No newline at end of file diff --git a/public/widgets/demos/orcid-widget-with-doi-shown.html b/public/widgets/demos/orcid-widget-with-doi-shown.html new file mode 100644 index 0000000..9a14c96 --- /dev/null +++ b/public/widgets/demos/orcid-widget-with-doi-shown.html @@ -0,0 +1,26 @@ + + + + + Paper view snippet example | Paper Badger + + + +
+ + + + \ No newline at end of file diff --git a/public/widgets/demos/orcid-widget-with-name-shown.html b/public/widgets/demos/orcid-widget-with-name-shown.html new file mode 100644 index 0000000..29be50a --- /dev/null +++ b/public/widgets/demos/orcid-widget-with-name-shown.html @@ -0,0 +1,25 @@ + + + + + Paper view snippet example | Paper Badger + + + +
+ + + + \ No newline at end of file diff --git a/public/widgets/widget.js b/public/widgets/widget.js new file mode 100644 index 0000000..8a5cc68 --- /dev/null +++ b/public/widgets/widget.js @@ -0,0 +1,355 @@ +/* exported PaperBadgerWidget */ +/* global ActiveXObject: false */ +/** + * PaperBadger widget + * Displays all badges for a DOI or ORCID. + * + * @param {Object} settings + * @param {string=} settings.DOI + * @param {string=} settings.ORCID + * @param {string} settings.containerClass + * @param {string=} settings.ORCIDWidgetType + * @param {string=} settings.loaderText + * @param {string=} settings.removeClass + * @param {Function=} settings.clickCallback + * @constructor + */ +var PaperBadgerWidget = function (settings) { + /** + * Widget container element + * @type {Element} + */ + var containerElement; + + /** + * Determines whether the ORCID widget shows the + * author's name or DOIs + * @type {string} + */ + var ORCIDWidgetType = 'default'; + + /** + * String shown in the loading animation + * @type {string} + */ + var loaderText = 'loading widget'; + + /** + * Element for the loading animation + * @type {Element} + */ + var loaderElement; + + /** + * Contains the interval id for the animation loop + * @type {Number} + */ + var loaderInterval; + + /** + * Checks the settings given to the function and starts + * the widget creation. + */ + var construct = function () { + // fix for #135, all but the first forward slash of a DOI + // should be encoded as %2F + if (settings.hasOwnProperty('DOI')) { + var tmp = settings.DOI.split('/'); + settings.DOI = tmp[0] + '/' + tmp.slice(1).join('%2F'); + } + + containerElement = document.getElementsByClassName(settings.containerClass)[0]; + + // Changes the ORCID widget type to DOI if the user requests it. + if (settings.hasOwnProperty('ORCIDWidgetType') && settings.ORCIDWidgetType === 'DOI') { + ORCIDWidgetType = 'DOI'; + } + + if (settings.hasOwnProperty('loaderText')) { + loaderText = settings.loaderText; + } + + // we need a container element and either a DOI or an ORCID + // proceed if these are available, throw an error if not + if (containerElement && (settings.hasOwnProperty('DOI') || settings.hasOwnProperty('ORCID'))) { + insertCSS(); + startLoader(); + loadWidgetContent(); + } else { + throw 'The settings object is incomplete. You need to supply an element id and either a DOI or an ORCID.'; + } + }; + + /** + * Injects a style tag into the header containing the necessary + * styles for the widget. + */ + var insertCSS = function () { + var css = '.paper-badge {float: left; width: 10em; height: 20em; overflow: hidden; ' + + 'border-top: 1px solid #ccc; height 15em; padding: 2%; margin-right: 1%; margin-top: 2%}' + + '.paper-badge a {width: 100%; display: inline-block; font-size: 88%; line-height: 1.2; ' + + 'color: #333; padding: 0.4em; cursor: pointer; text-decoration: none} .paper-badge ' + + 'a.active {color: #fff; background: #7ab441; text-decoration: none} .paper-badge img ' + + '{max-width: 8em; margin-left: 10%; margin-bottom: 1%} .paper-badges-hidden{display: none}'; + + var head = document.head || document.getElementsByTagName('head')[0]; + var style = document.createElement('style'); + style.type = 'text/css'; + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } + + head.appendChild(style); + }; + + /** + * Inserts the loader element into the container element + * and starts an animation loop. + */ + var startLoader = function () { + loaderElement = document.createElement('div'); + loaderElement.className = 'paperbadger-loader'; + loaderElement.setAttribute('data-step', '4'); + containerElement.appendChild(loaderElement); + loaderMethod(); + + loaderInterval = setInterval(loaderMethod, 750); + }; + + /** + * Animation loop for the loader text. Adds / removes + * dots from the loader text. + */ + var loaderMethod = function () { + var step = loaderElement.getAttribute('data-step'); + var text = loaderText; + + if (step === '4') { + step = 1; + } else if (step === '1') { + step = 2; + text += '.'; + } else if (step === '2') { + step = 3; + text += '..'; + } else if (step === '3') { + step = 4; + text += '...'; + } + + loaderElement.innerHTML = text; + loaderElement.setAttribute('data-step', step); + }; + + /** + * Stops the loader animation loop. + */ + var stopLoader = function () { + clearInterval(loaderInterval); + }; + + /** + * Loads the widget content from the Badges API. + */ + var loadWidgetContent = function () { + var url = getEndpoint(); + var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); + + xmlhttp.onreadystatechange = function () { + if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { + stopLoader(); + processAjaxResponse(xmlhttp.responseText); + } + }; + + xmlhttp.open('GET', url, true); + xmlhttp.send(); + }; + + /** + * Generates the endpoint url depending on the + * widget settings. + * + * @returns {string} + */ + var getEndpoint = function () { + var url = 'https://badges.mozillascience.org/'; + + if (settings.DOI) { + url += 'papers/' + settings.DOI; + } else if (settings.ORCID) { + url += 'users/' + settings.ORCID; + } else { + throw 'You need to supply either a DOI or an ORCID.'; + } + + url += '/badges'; + + return url; + }; + + /** + * Processes the data from the Badges API so it + * can be rendered in the next step. + * + * @param {string} json + */ + var processAjaxResponse = function (json) { + var badgesJson = JSON.parse(json); + var badges = {}; + var isDOI = (settings.hasOwnProperty('DOI') && settings.DOI.length); + + for (var i = 0; i < badgesJson.length; i++) { + var badgeId = badgesJson[i].badge.slug; + if (!badges.hasOwnProperty(badgeId)) { + badges[badgeId] = { + name: badgesJson[i].badge.name, + consumerDescription: badgesJson[i].badge.consumerDescription, + imageUrl: badgesJson[i].badge.imageUrl, + links: [] + }; + } + + // depending whether we have a DOI widget, an ORCID default widget + // ( = displays names) or an ORCID display a list + // of either authors or articles for a badge + if (isDOI || ORCIDWidgetType === 'default') { + badges[badgeId].links.push({ + id: badgesJson[i].orcid, + type: 'ORCID', + taxonomy: badgeId, + text: badgesJson[i].authorName, + url: 'https://orcid.org/' + badgesJson[i].orcid + }); + } else { + var localDOI = badgesJson[i].evidenceUrl.split('doi.org/')[1]; + + badges[badgeId].links.push({ + id: localDOI, + type: 'DOI', + taxonomy: badgeId, + text: localDOI, + url: badgesJson[i].evidenceUrl.replace('http:', 'https:') + }); + } + } + + renderBadges(badges); + }; + + /** + * Generates and displays the HTML code for the received + * badge data. + * + * @param {Object} badges + */ + var renderBadges = function (badges) { + var i = 0; + var html = ''; + var numBadges = 0; + + for (var key in badges) { + if (badges.hasOwnProperty(key)) { + numBadges++; + + html += '
'; + html += '' + badges[key].name + ''; + + for (i = 0; i < badges[key].links.length; i++) { + html += '' + badges[key].links[i].text + ''; + } + + html += '
'; + } + } + + containerElement.innerHTML = html; + + // if there were any badges and if the user supplied + // removeClass in the settings, remove the class from + // all elements on the page + if (numBadges && settings.hasOwnProperty('removeClass') && settings.removeClass.length) { + var elements = document.getElementsByClassName(settings.removeClass); + + for (i = 0; i < elements.length; i++) { + var classes = elements[i].className.split(' '); + for (var j = 0; j < classes.length; j++) { + if (classes[j] === settings.removeClass) { + classes.splice(j, 1); + } + } + elements[i].className = classes.join(' '); + } + } + + // add mouseover and clickCallbacks for all links + var links = document.getElementsByClassName('paper-badger-link'); + for (i = 0; i < links.length - 1; i++) { + links[i].addEventListener('mouseover', mouseOverMethod); + links[i].addEventListener('click', clickCallbackMethod); + } + }; + + /** + * Marks all occurrences of an author on mouse over + * by adding a .active CSS class to the link element. + */ + var mouseOverMethod = function () { + var id = this.getAttribute('data-id'); + var links = document.getElementsByClassName('paper-badger-link'); + + for (var i = 0; i < links.length; i++) { + var j = 0; + var classes = links[i].className.split(' '); + + if (links[i].getAttribute('data-id') === id) { + var found = false; + for (j = 0; j < classes.length - 1; j++) { + if (classes[j] === 'active') { + found = true; + break; + } + } + + if (!found) { + classes.push('active'); + } + } else { + for (j = classes.length; j > 0; j--) { + if (classes[j] === 'active') { + classes.splice(j, 1); + } + } + } + + links[i].className = classes.join(' '); + } + }; + + /** + * Calls the clickCallback method if the user supplied one + */ + var clickCallbackMethod = function () { + if (settings.hasOwnProperty('clickCallback') && settings.clickCallback) { + var data = { + taxonomy: this.getAttribute('data-taxonomy') + }; + + if (this.getAttribute('data-type') === 'DOI') { + data.doi = this.getAttribute('data-id'); + } else if (this.getAttribute('data-type') === 'ORCID') { + data.orcid = this.getAttribute('data-id'); + } + + settings.clickCallback(data); + } + }; + + construct(); +};