Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
shane committed Jan 24, 2019
0 parents commit 15ba98d
Show file tree
Hide file tree
Showing 12 changed files with 7,713 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
globals: {
Promise: true
}
};
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
121 changes: 121 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# @zaneray/modal-extras


## Getting Started

Modal Extras can be used in an project that implements Bootstrap modal

```
yarn add @zaneray/modal-extras
```

and then import the functions you wish to use through ES6 imports:

```
import * as modalextras from '@zaneray/modal-extras`;
```

If you prefer not to use a package manager, you can download the latest version of Modal Extras and include it in your project manually from the following links:

- [modal-extras.js](http://unpkg.com/@zaneray/modal-extras@latest/dist/modal-extras.js)
- [theme-cart.min.js](http://unpkg.com/@zaneray/modal-extras@latest/dist/modal-extras.min.js)

These files make Modal Extras accessible via the `Zaneray.bootstrap.modal` global variable.

## Browser Support

Modal Extras uses two APIs not available to legacy browsers, Fetch and Promise. If you wish to support legacy browsers, make sure you add the following dependencies to your project:

```
yarn add unfetch es6-promise
```

and then import them before you import Modal Extras:

```js
// Only need to import these once
import 'unfetch/polyfill';
import 'es6-promise/auto';

// Import @zaneray/modal-extras anywhere you need it
import * as modalextras from '@zaneray/modal-extras';
```

## Methods

### onLoad()

Finds all data-toggle elements on the page and binds them to the proper methods based on their value

```js
modalextras.onLoad();
```

### generateModalTemplate(html, clazz)

Injects the dynamic-modal HTML wrapper to the begining of the body and binds a modal.bs.hide method to remove it from the DOM

```js
modalextras.generateModalTemplate('<div>content</div>', '');
```

## Binding Usage

### Inline Html

Auto bind a link on the page to load content on the page to a modal. To avoid duplicate DOM id's on the page
when the content is copied in to the modal, the parent element ID is changed to [id]-modal. If you're relying
on this id for styling, ensure you also include this selector in your css.

```html
<a href="#" data-toggle="modal-html" data-id="id-1234">Open HTML modal by ID</a>
<div id="id-1234">Content to load in a modal</div>
```

### AJAX Content
By default you can specify a URL and all of that URL will be loaded to the page. Specify data-toggle of modal-ajax
and the script will grab the URL in the href. This works fine for snippets of HTML but in demo you can see that it
loads all of the image from /includes/ajax.html including the header and footer.

```html
<a href="includes/ajax.html" data-toggle="modal-ajax">Open Ajax Modal</a>
```

#### Load a page with an optional selector

Optionally you can specify an id and the script will load the whole page and display the results specified in the data-id selector.

```html
<a href="includes/ajax.html" data-toggle="modal-ajax" data-id="wrapper">Open Ajax Modal and inject with Element id="wrapper" from response</a>
```

### Modal Images

Specify a data-toggle attribute of modal-image to load the image in the href of an anchor link in a modal window.

```html
<a href="http://www.zaneray.com/proto/images/flatheadsunset.jpg" data-toggle="modal-image">Open horizontal Image Modal </a>
```

### Modal Videos

Specify a data-toggle of modal-video to dynamically load the video. It is required to specify a data-key as well as a data-source which at the moment only includes Youtube and Vimeo. It is recommended to make the href a link to the URL of the video for Accessbility reasons.

```html
<a href="https://vimeo.com/74980365" data-key="74980365" data-source="Vimeo" data-toggle="modal-video">Open Vimeo Video</a>
```

### Additional Classes

If you need to override the styling of a modal window you can always specify a custom class with data-class attribute.

```html
<a href="http://www.zaneray.com/proto/images/flatheadsunset.jpg" data-toggle="modal-image" data-class="my-additional-class">Open Image with Custom Class </a>
```

### Closing the modal window

Pressing escape will close the Modal window for all dynamically created modal windows not including standard inline Bootstrap Modals. Click outside the modal
window will also close the modal.

The hidden.bs.modal event is bound to remove the modal from the DOM upon close
252 changes: 252 additions & 0 deletions dist/modal-extras.cjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var $ = _interopDefault(require('jquery'));

/**
*
* @export
*/
function onLoad(){
bindActions();
closeModalOnEscape();
}

const BASE_MODAL = '<div id="dynamic-modal" class="modal fade"><div class="modal-dialog"><div class="modal-btn-close btn-close" data-dismiss="modal"></div><div id="modal-content" class="modal-content"></div></div></div>';

/**
* Checks to see if the modal by ID is already in the dom. If so, uses it
* If not, inserts after the opening body tag
*
* @export
* @param {*} html the HTML to inject in to the modal content area
* @param {*} classname a classname to add to the modal element
*/
function generateModalTemplate(html, className){
let modal = getModal();

if (!modal){
document.body.insertAdjacentHTML('afterbegin', BASE_MODAL);
modal = getModal();
}

if (html) {
modal.querySelector('.modal-content').innerHTML = html.outerHTML;
}

if (className) {
className.split(' ').forEach(element => {
if ( element && element.length){
modal.classList.add(element);
}
});
}

// addEventlistener will not fire the hidden.bs.modal event due to how
// bootstrap binds the events with .trigger instead of fireEvent
$(modal).modal().on('hidden.bs.modal', function() {
getModal().remove();
});
}

function getModal(){
return document.getElementById('dynamic-modal');
}

function getModalContent(){
return getModal().querySelector('.modal-content');
}

/**
* Finds all DOM elements with data-toggle and hooks their
* click even to trigger the appropriate method
* based on the data-toggle value.
*
* The data-toggle value is lowercased, and has hyphens
* removed, and then the Element is passed to that function
*
* Because there could be other data-toggle's on the page
* for accordions, etc, we make sure that we only preventDefault
* and stopPropagation when we know we have a link that
* we want to handle.
*/
function bindActions() {
document.body.addEventListener('click', (evt) => {
evt = evt || window.event;
if (evt.target !== evt.currentTarget){
if (!evt.target.hasAttribute('data-toggle')) return;
const modalType = String(evt.target.dataset.toggle.replace(/-/g,'')).toLowerCase();
console.log(modalType);
switch (modalType){
case 'modalhtml': {
evt.preventDefault();
modalhtml(evt.target);
evt.stopPropagation();
break;
}
case 'modalajax': {
evt.preventDefault();
modalajax(evt.target);
evt.stopPropagation();
break;
}
case 'modalvideo': {
evt.preventDefault();
modalvideo(evt.target);
evt.stopPropagation();
break;
}
case 'modalimage': {
evt.preventDefault();
modalimage(evt.target);
evt.stopPropagation();
break;
}
default:
console.log(`${modalType} not a valid modal-extras data-toggle, skipping`);
break;
}
}
});
}

/**
* Adds an event listener to the document to check to see
* that the key pressed is the escape key. If so, and if
* the modal element is visible, it triggers a click
* on the modals btn-close link
*/
function closeModalOnEscape() {
document.addEventListener('keyup', (evt) => {
evt = evt || window.event;
var isEscape = false;
if ("key" in evt) {
isEscape = (evt.key == "Escape" || evt.key == "Esc");
} else {
isEscape = (evt.keyCode == 27);
}
const modal = getModal();
if (isEscape && modal && !modal.hidden) {
modal.querySelector('.modal-btn-close').click();
}
});
}

/**
* Bind all links that open a modal from an
* HTML element that lives in the current page
*
* @param {*} el the HTML object that was clicked
*/
function modalhtml(el){
let html = '';
const id = el.dataset.id;
const clazz = el.dataset.class || '';
const targetElement = document.getElementById(id);

if(targetElement){
html = targetElement.cloneNode(true);
html.id = html.id + '-modal';
}

generateModalTemplate(html, clazz);
}

/**
* Content is loaded from an ajax call
*
* @param {*} el the HTML object that was clicked
*/
function modalajax(el){
let pageURL = el.href;
const clazz = el.dataset.class || '';
const contentId = el.dataset.id;
const unwrap = el.dataset.unwrap || false;

generateModalTemplate('', 'modal-ajax modal-loading ' + clazz);

fetch(pageURL).then(response =>{
if (!response.ok) {
throw Error(response.statusText);
}
return response.text();
}).then(html => {
let inject = html;
if(typeof contentId !== 'undefined') {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
try {
inject = unwrap ? doc.querySelector(contentId).innerHTML : doc.querySelector(contentId).outerHTML;
}
catch(e){
inject = `id='${contentId}' was not found in the response from ${pageURL}`;
}
}
getModalContent().innerHTML = inject;
getModal().classList.remove('modal-loading');
})
.catch(err => {
console.log(err);
alert('there was an error loading the URL');
getModal().remove();
});

}

/**
* Content is a video coming either from YouTube or Vimeo.
*
* @param {*} el the HTML object that was clicked
*/
function modalvideo(el) {
const clazz = el.dataset.class || '';
const videoSource = el.dataset.source;
const videoKey = el.dataset.key;
let videoHTML, embedURL;

switch (videoSource.toLowerCase()) {
case 'vimeo':
embedURL = `https://player.vimeo.com/video/${videoKey}?autoplay=1&title=0&byline=0&portrait=0`;
break;
case 'youtube':
embedURL = `https://www.youtube.com/embed/${videoKey}?rel=0&amp;showinfo=0&autoplay=1`;
break;
default:
alert(`The video source '${videoKey}' is not valid (must be vimeo or youtube)`);
}

videoHTML = `<div class="modal-video-wrapper"><iframe src="${embedURL}" class="modal-video-iframe" width="720" height="405" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>`;
const videoElement = document.createElement('div');
videoElement.innerHTML = videoHTML;
generateModalTemplate(videoElement.firstChild, 'modal-video ' + clazz);
}

/**
* Content is image(s) on the page or
* coming from Instagram
*
* @param {*} el the HTML object that was clicked
*/
function modalimage(el) {
const $this = $(el);
const clazz = el.dataset.class || '';
const img = new Image();
img.title = el.title || '';
img.id = 'modal-image';
img.classList.add('modal-image');
img.addEventListener('load', function() {
getModal().classList.remove('modal-loading');
}, false);
img.src = el.href;

generateModalTemplate(img, 'modal-image-wrapper modal-loading ' + clazz);

/* TODO add more functionality */

}

exports.onLoad = onLoad;
exports.generateModalTemplate = generateModalTemplate;
Empty file added dist/modal-extras.css
Empty file.
Loading

0 comments on commit 15ba98d

Please sign in to comment.