/**
* @file main.js
* @method ModalCarousel
* @desc Accesses the Flickr API to display a grid list of images that are clickable to launch a dialog. The dialog then has a carousel effect to quickly move through the larger images.
* @author Jim O'Harra-Sutton
* @since 7/06/16 10:33 AM
*
* @constructs ModalCarousel
* @returns {Object} this
*/
var MC,
ModalCarousel = function () {
'use strict';
var self = this;
/**
* @class Details
* @desc Provides a polyfill for the <details> tag
*
* @type {Object}
* @memberof ModalCarousel
*
* @constructs Details
*/
this.Details = {
/**
* @method init
* @desc Initializes a <details> polyfill
*
* @memberof ModalCarousel.Details
*
* @borrows this.detailsPolyFill()
*/
init: function () {
var root = document.getElementsByTagName('body');
if (root.length) {
root = root[0];
if (root) {
this.detailsPolyFill(root.ownerDocument);
}
}
},
/**
* @method handleSummaryOpen
* @desc Collects a <summary> tag and sets the closed attribute if the item is currently open
*
* @memberof ModalCarousel.Details
*
* @param e {Object} root object
*
*/
handleSummaryOpen: function (e) {
var target = e.target,
details;
if (target.nodeName !== 'SUMMARY') {
return;
} else {
if (e.type === 'keypress' && e.which !== 13 && e.which !== 32) {
return;
}
details = target.parentNode;
if (details.getAttribute('closed')) {
details.removeAttribute('closed');
} else {
details.setAttribute('closed', 'closed');
}
}
},
/**
* @method detailsPolyFill
* @desc Collects <summary> tags and sets click bindings, fulfilling the polyfill
*
* @memberof ModalCarousel.Details
*
* @param doc {Object} Target element to initiate the search for <summary> tags
*
* @borrows this.handleSummaryOpen()
*/
detailsPolyFill: function (doc) {
var summaries,
len,
i;
summaries = doc.querySelectorAll('summary');
len = summaries.length;
for (i = 0; i < len; i++) {
summaries[i].setAttribute('tabindex', '0');
/**
* @event {summary} click
* @memberof ModalCarousel.Details
*/
summaries[i].addEventListener('click', this.handleSummaryOpen);
/**
* @event {summary} keypress
* @memberof ModalCarousel.Details
*/
summaries[i].addEventListener('keypress', this.handleSummaryOpen);
}
}
};
/**
* @class Flickr
* @desc Connects to the Flickr API and processes the info
*
* @type {Object}
* @memberof ModalCarousel
*
* @constructs Flickr
*/
this.Flickr = {
url: 'https://api.flickr.com/services/feeds/photos_public.gne?tags=nature&format=json',
/**
* @method fetch
* @desc Sets the <script> tag for a JSONP-like, asynchronous Flickr API call
*
* @memberof ModalCarousel.Flickr
*
* @param url {String} ModalCarousel.Flickr.url
*/
fetch: function (url) {
var api = document.createElement('script');
api.src = url;
api.async = true;
api.defer = true;
document.getElementsByTagName('body')[0].appendChild(api);
},
/**
* @method createSection
* @desc Creates a set of elements for a new <section>
*
* @memberof ModalCarousel.Flickr
*
* @returns {Object} section -> details -> summary -> article -> ul
*/
createSection: function () {
return {
section: document.createElement('section'),
details: document.createElement('details'),
summary: document.createElement('summary'),
article: document.createElement('article'),
ul: document.createElement('ul')
};
},
/**
* @method applySection
* @desc Applies the DOM returned from this.createSection()
*
* @memberof ModalCarousel.Flickr
*
* @param data {Object} returned from window.jsonFlickrFeed()
* @borrows this.createItem()
* @borrows this.applyArticle()
*/
applySection: function (data) {
var newSection = this.createSection(),
title = document.createTextNode(data.title),
closed = document.getElementById('Closed'),
main = document.getElementsByTagName('main')[0];
// Section & details
newSection.summary.appendChild(title);
newSection.details.appendChild(newSection.summary);
newSection.section.appendChild(newSection.details);
newSection.section.appendChild(newSection.article);
newSection.article.appendChild(newSection.ul);
main.appendChild(newSection.section);
self.Details.init();
closed.disabled = true;
this.applyArticle(data, newSection);
},
/**
* @method applyArticle
* @desc loops through the API data
*
* @memberof ModalCarousel.Flickr
*
* @param data {Object} passed from this.applySection()
* @param newSection {Object} DOM object passed from this.applySection()
*
* @borrows this.sortItem()
* @borrows this.createDialog()
*/
applyArticle: function (data, newSection) {
var i,
newItem,
item,
itemTitle;
for (i = 0; i < data.items.length; i++) {
newItem = this.createItem();
item = this.sortItem(data.items[i]);
itemTitle = document.createTextNode(item.title || i);
newSection.ul.appendChild(newItem.li);
newItem.li.appendChild(newItem.figure);
newItem.figure.appendChild(newItem.figcaption);
newItem.figcaption.appendChild(itemTitle);
newItem.figure.appendChild(newItem.label);
newItem.label.setAttribute('for', i.toString());
newItem.label.appendChild(newItem.img);
newItem.img.src = item.img;
newItem.img.title = item.title;
this.createDialog(i, item, (data.items.length - 1));
}
},
/**
* @method createItem
* @desc Creates a set of elements for a new <section>
*
* @memberof ModalCarousel.Flickr
*
* @returns {Object} li -> figure -> figcaption, label -> img
*/
createItem: function () {
return {
label: document.createElement('label'),
img: document.createElement('img'),
li: document.createElement('li'),
h2: document.createElement('h2'),
figure: document.createElement('figure'),
figcaption: document.createElement('figcaption')
};
},
/**
* @method createDialog
* @desc Puts together the DOM for a Modal dialog
*
* @memberof ModalCarousel.Flickr
*
* @param i {int} Counter, passed from this.applyArticle()
* @param data {Object} Flickr data sent from this.applyArticle()
* @param length {int} Flickr array length sent from this.applyArticle()
*
* @borrows this.handleCarouselButtons()
* @borrows this.handleClicks()
*
*/
createDialog: function (i, data, length) {
var content = document.querySelector('template').content,
input = content.querySelector('input'),
dialog = content.querySelector('dialog'),
aside = dialog.querySelector('aside'),
h2 = aside.querySelector('h2'),
div = aside.querySelector('div'),
nav = dialog.querySelector('nav'),
prev = nav.querySelectorAll('label')[0],
next = nav.querySelectorAll('label')[1],
closed = document.getElementById('Closed'),
closeButton,
img;
input.id = i;
dialog.id = 'Dialog' + i;
h2.textContent = data.title;
div.innerHTML = data.description.replace('_m.jpg', '_b.jpg');
img = div.querySelector('img');
img.removeAttribute('width');
img.removeAttribute('height');
this.handleCarouselButtons(i, length, next, prev);
document.body.appendChild(document.importNode(content, true));
closeButton = document.getElementById('Dialog' + i).querySelector('label span');
this.handleClicks(i, closed, closeButton);
},
/**
* @method handleCarouselButtons
* @desc Sets the Previous and Next input items for the Carousel arrows
*
* @memberof ModalCarousel.Flickr
*
* @param i {int} Counter, passed from this.applyArticle()
* @param length {int} item length passed from this.applyArticle()
* @param next {Object} Next arrow
* @param prev {Object} Previous arrow
*/
handleCarouselButtons: function (i, length, next, prev) {
if (i === length) {
next.setAttribute('for', '0');
} else {
next.setAttribute('for', (i + 1).toString());
}
if (i === 0) {
prev.setAttribute('for', length.toString());
} else {
prev.setAttribute('for', (i - 1).toString());
}
},
/**
* @method handleClicks
* @desc Sets the <dialog> background as clickable to close the Dialog
*
* @memberof ModalCarousel.Flickr
*
* @param i {int} Counter, passed from this.applyArticle()
* @param closed {Object} #Closed <input> item to mark "none" selected
* @param closeButton {Object} (X) button in the upper right of the Dialog
*/
handleClicks: function (i, closed, closeButton) {
var dialog = document.getElementById('Dialog' + i);
/**
* @event {dialog->aside} click
* @memberof ModalCarousel.Flickr
*/
dialog.querySelector('aside').addEventListener('click', function (ev) {
ev.stopPropagation();
});
/**
* @event {dialog} click
* @memberof ModalCarousel.Flickr
*/
dialog.addEventListener('click', function () {
closed.checked = true;
closed.disabled = true;
});
/**
* @event {closeButton} click
* @memberof ModalCarousel.Flickr
*/
closeButton.addEventListener('click', function () {
closed.checked = true;
closed.disabled = true;
});
},
/**
* @method sortItem
* @desc Sanitizes the data sent from window.jsonFlickrFeed()
*
* @memberof ModalCarousel.Flickr
*
* @param item {int} Counter, passed from this.applyArticle()
* @returns sortedItem {Object} title, description & img
*/
sortItem: function (item) {
var sortedItem = {
title: item.title,
description: item.description
};
if (item.media !== null && item.media.m !== null) {
sortedItem.img = item.media.m;
}
return sortedItem;
}
};
return this;
};
(function () {
'use strict';
/**
* @global
* @callback jsonFlickrFeed
* @desc Callback method used when the Flickr API has loaded the data
*
* @memberof window
*
* @param rsp {Object} JSON formatted response data
* @borrows ModalCarousel.Flickr.applySection()
*/
window.jsonFlickrFeed = function (rsp) {
if (rsp.title !== '') {
MC.Flickr.applySection(rsp);
}
};
MC = new ModalCarousel();
MC.Flickr.fetch(MC.Flickr.url);
}());