Fixed closing issue with cards.

This commit is contained in:
Severin Opel 2019-08-14 17:46:12 +02:00
parent 30c7113713
commit 28a7a0b6a2
6 changed files with 375 additions and 262 deletions

43
dist/iwmlib.js vendored
View File

@ -2772,7 +2772,9 @@
for (let j = 0; j < elements.length; j++) {
// if(elements[j].tagName == "svg") return false;
let hammer = new Hammer(elements[j], opts);
const target = elements[j];
let hammer = new Hammer(target, opts);
if (window.propagating !== 'undefined') {
hammer = propagating(hammer);
@ -2793,16 +2795,23 @@
hammer.get('tap').set(opts);
}
console.log('APPLY HAMMER ON', type);
target.addEventListener("click", ()=>{
console.log("Hello");
});
hammer.on(type, event => {
console.log('FIRED');
cb(event);
});
if (Hammer.__hammers.has(elements[j])) {
const elementHammers = Hammer.__hammers.get(elements[j]);
if (Hammer.__hammers.has(target)) {
const elementHammers = Hammer.__hammers.get(target);
elementHammers.push(hammer);
Hammer.__hammers.set(elements[j], elementHammers);
Hammer.__hammers.set(target, elementHammers);
} else {
Hammer.__hammers.set(elements[j], [hammer]);
Hammer.__hammers.set(target, [hammer]);
}
}
} else {
@ -7709,8 +7718,9 @@
* @memberof Card
*/
static _replaceAttributes(context, html, attribute, replaceFunc) {
let clickables = html.querySelectorAll(`[${attribute}]`);
clickables.forEach(element => {
let attributeCarrier = html.querySelectorAll(`[${attribute}]`);
attributeCarrier.forEach(element => {
console.log(element);
let attributeVal = element.getAttribute(attribute);
element.removeAttribute(attribute);
replaceFunc.call(this, context, element, attributeVal);
@ -7759,10 +7769,13 @@
// These are 'hardcoded' inside the convert.js.
if (element.tagName == 'circle') return false
console.log("Replace" , context, element, attributeVal);
this.registerEvent(context, interactionType, element, event => {
/**
* Replaces the strings from the listener with the cooresponding variables.
*/
console.log("EVENT");
let args = [];
argsStrings.forEach(arg => {
arg = arg.trim();
@ -7811,13 +7824,6 @@
/<\s*(a|video|img|image|circle)\s(.*?)(xlink:href|href|src)\s*=\s*["'](\..*?)["']\s*(.*?)>/g,
function(data) {
let path = that._getRelativePath(arguments[4]);
console.log('REPLACE ', arguments[1]);
if (arguments[1] == 'a') {
console.error('NOT REPLACING LINKS');
return ''
}
const tag = `<${arguments[1]} ${arguments[2]} ${arguments[3]}="${path}" ${arguments[5]}>`;
/* if (that.debug) */ console.log('Adjusted: ', tag);
return tag
@ -9152,7 +9158,7 @@
* @memberof Card
*/
static openIndexCard(event, src) {
//console.log("openIndexCard", src)
console.log("openIndexCard", src);
/*
* Called by the expandIndexCard(...)
*/
@ -9324,6 +9330,7 @@
let parser = new DOMParser();
let html = parser.parseFromString(htmlString, 'text/html');
if (!editable) {
console.log("REPLACE ONCLICK");
this._replaceAttributes(context, html, 'onclick', this._replaceCallback);
}
let zoomableWrappers = html.querySelectorAll('.svg-wrapper');
@ -9562,7 +9569,10 @@
* @memberof Card
*/
static registerEvent(context, types, element, callback) {
InteractionMapper.on(types, element, callback);
console.log("REGISTER INTERACTION EVENT",context, types, element, callback);
InteractionMapper.on(types, element, ()=>{
console.log("HELLO");
});
if (context._registeredEvents == null) context._registeredEvents = [];
if (context._registeredEvents.indexOf(element) == -1) context._registeredEvents.push(element);
}
@ -10202,6 +10212,7 @@
context.classList.add('info-card');
this.relativePath = basePath;
console.log(htmlString);
htmlString = this._adjustRelativeLinks(htmlString);
let parser = new DOMParser();

19
dist/iwmlib.pixi.js vendored
View File

@ -6396,7 +6396,9 @@
for (let j = 0; j < elements.length; j++) {
// if(elements[j].tagName == "svg") return false;
let hammer = new Hammer(elements[j], opts);
const target = elements[j];
let hammer = new Hammer(target, opts);
if (window.propagating !== 'undefined') {
hammer = propagating(hammer);
@ -6417,16 +6419,23 @@
hammer.get('tap').set(opts);
}
console.log('APPLY HAMMER ON', type);
target.addEventListener("click", ()=>{
console.log("Hello");
});
hammer.on(type, event => {
console.log('FIRED');
cb(event);
});
if (Hammer.__hammers.has(elements[j])) {
const elementHammers = Hammer.__hammers.get(elements[j]);
if (Hammer.__hammers.has(target)) {
const elementHammers = Hammer.__hammers.get(target);
elementHammers.push(hammer);
Hammer.__hammers.set(elements[j], elementHammers);
Hammer.__hammers.set(target, elementHammers);
} else {
Hammer.__hammers.set(elements[j], [hammer]);
Hammer.__hammers.set(target, [hammer]);
}
}
} else {

View File

@ -1,5 +1,6 @@
'use strict'
'use strict';
import Highlight from './highlight.js'
/** To avoid problems with relative URL paths, we use inline data URI to load svg icons. */
const closeIconDataURI = `data:image/svg+xml;utf8,
@ -33,10 +34,17 @@ const enableNearestNeighborTaps = false
export default class Card {
static setup(context, modules = []) {
console.log('Setup Card...', modules)
context.modules = []
/**
* This is required for the callback functions to work properly.
*/
window.Card = Card
context.modules = []
context.module = {}
context.onClose = null
context.classList.add('info-card')
context.setAttribute('data-id', Card.id++)
@ -51,16 +59,57 @@ export default class Card {
static remove(context) {
for (let module of Object.values(context.module)) {
module.remove()
const moduleHasRemoveFunction = typeof module.remove === 'function'
if (moduleHasRemoveFunction) module.remove()
}
}
static eventClose(event) {
static close(event) {
let context = this.getContext(event.target)
if (context) {
this.constructor.close(context)
} else console.error('Could not find context!', event.target)
if (context.onClose != null) {
context.onClose()
} else {
this.remove(context)
}
// console.error("Remove")
// let context = this.getContext(event.target)
// if (context) {
// this.remove(context)
// } else console.error('Could not find context!', event.target)
}
/**
* Adds an on close method to the provided context.
* This will overwrite the default closing behaviour.
* Removing the
*
* @static
* @param {DOMElement} context - Context on which the onClose will be set.
* @param {Function} callback - Callback function of the onClose.
* @memberof Card
*/
static setOnClose(context, callback) {
if (context.onClose != null) console.error('OnClose was already set. It was overwritten by the new method.')
context.onClose = callback
}
/**
* Unsets the onClose.
*
* Note: This may be used in conjunction with the setOnClose method.
* Using the setOnClose method to adjust behaviour before closing the card.
* Then unsetting the onClose to close the Card appropriately by calling the
* Card.Close again.
*
* @static
* @param {DOMElement} context - Context on which the remove will be executed.
* @memberof Card
*/
static removeOnClose(context) {
context.onClose = null
}
/**
@ -70,12 +119,12 @@ export default class Card {
* @param {*} event
* @memberof Card
*/
static close(context) {
console.log('CLOSE CARD!!!')
this.unregisterAllEvents(context)
if (context.onClose) {
context.onClose(event)
} else context.parentNode.removeChild(context)
static remove(context) {
if (context.parentNode != null) {
context.parentNode.removeChild(context)
} else {
console.error('Tried removing card but it was already removed.')
}
}
/**
@ -88,8 +137,8 @@ export default class Card {
* @memberof Card
*/
static _replaceAttributes(context, html, attribute, replaceFunc) {
let clickables = html.querySelectorAll(`[${attribute}]`)
clickables.forEach(element => {
let attributeCarrier = html.querySelectorAll(`[${attribute}]`)
attributeCarrier.forEach(element => {
let attributeVal = element.getAttribute(attribute)
element.removeAttribute(attribute)
replaceFunc.call(this, context, element, attributeVal)
@ -124,11 +173,23 @@ export default class Card {
let argsStrings = trimmedArgs.split(',').filter(entry => {
return entry.trim() != ''
})
/**
* As we determine a function by a string we must traverse from the window object to
* get the associated javascript function.
*/
let callStack = window
let last = 'window'
do {
callStack = callStack[callParts.shift().trim()]
let func = callParts.shift().trim()
if (callStack[func] == null) {
callStack = null
console.error(
`Could not access callback function: ${attributeVal}. Member ${func} of ${last} could not be found.`
)
break
} else callStack = callStack[func]
} while (callParts.length > 0)
let targetFunc = callStack
let that = this
@ -161,8 +222,9 @@ export default class Card {
}
})
event.stopPropagation()
if (callStack) callStack.call(that, ...args)
else {
if (targetFunc) {
targetFunc.call(that, ...args)
} else {
console.error('Could not call callback function ' + attributeVal, ...args)
}
})
@ -190,13 +252,6 @@ export default class Card {
/<\s*(a|video|img|image|circle)\s(.*?)(xlink:href|href|src)\s*=\s*["'](\..*?)["']\s*(.*?)>/g,
function(data) {
let path = that._getRelativePath(arguments[4])
console.log('REPLACE ', arguments[1])
if (arguments[1] == 'a') {
console.error('NOT REPLACING LINKS')
return ''
}
const tag = `<${arguments[1]} ${arguments[2]} ${arguments[3]}="${path}" ${arguments[5]}>`
/* if (that.debug) */ console.log('Adjusted: ', tag)
return tag
@ -751,7 +806,7 @@ export default class Card {
let point = svgPoint.matrixTransform(matrix)
let closestDiv = node.closest('div')
console.log('closestDiv', closestDiv, point)
// console.log('closestDiv', closestDiv, point)
let global = Points.fromNodeToPage(closestDiv, point)
let local = Points.fromPageToNode(context, global)
@ -763,7 +818,7 @@ export default class Card {
// we could load the data while the circle is animating.
// but for simplicity it's just done here for now.
// TODO: Adjust to load while animating (Problem: Unload when cancelled).
console.log('loadHighlightPopup', src, position, local)
// console.log('loadHighlightPopup', src, position, local)
this._loadPopupContent(context, src)
.then(content => {
this._openPopup(context, src, local, content, {
@ -1214,7 +1269,7 @@ export default class Card {
*
* @param {*} card - The card to expand
* @param {string} html - The original HTML of the card
* @param {*} tagName - The tagname of the element that is used as exanded element
* @param {*} tagName - The tagname of the element that is used as expanded element
* @param {*} src - The src of the expanded element
* @param {*} callback - A callback that is called when the expanded element is closed
*/
@ -1257,7 +1312,6 @@ export default class Card {
let scaleY = globalPreviewRect.height / globalIndexCardRect.height
let padding = parseInt(this.css(indexbox, 'padding'))
let maxWidth = this.css(card, 'max-width')
TweenLite.set(clone, {
css: {
@ -1295,7 +1349,6 @@ export default class Card {
if (this.dynamicHeight) {
let targetHeight = subcardContent.offsetHeight
console.log(targetHeight)
subcardContent.classList.add('dynamic-height')
/**
* Scale the content from 100% to it's target size.
@ -1309,7 +1362,7 @@ export default class Card {
}
//jquery hyphenate below
if (this.constructor._jQueryIsPresent()) {
if (this._isJQueryPresent()) {
$('.column')
.not('.overview')
.children('p')
@ -1389,7 +1442,7 @@ export default class Card {
if (enableNearestNeighborTaps) {
//look for nearby popups on tap
InteractionMapper.on('tap', indexbox, () => {
console.log('Tap handler called', editable)
// console.log('Tap handler called', editable)
if (!editable) {
this.findNearbyPopups(event, card)
}
@ -1407,24 +1460,55 @@ export default class Card {
if (isDirty) {
mainController.saveNode(html.innerHTML, url => {
callback(url)
this._closeIndexCard(context, card,{
this._closeIndexCard(context, card, clone, articleClone, {
eventElements,
src
})
})
} else {
this._closeIndexCard(context, card)
this._closeIndexCard(context, card, clone, articleClone)
}
} else {
this._closeIndexCard(context, card)
this._closeIndexCard(context, card, clone, articleClone)
}
})
}
static _closeIndexCard(context, card, {
eventElements = [],
src = null
} = []) {
/**
* Closes the index card again.
*
* @static
* @param {DOMElement} context - The Card element.
* @param {DOMElement} subcard - The original subcard element visible on the main card.
* @param {DOMElement} clonedSubcard - The cloned subcard that's going to be expanded.
* @param {DOMElement} clonedArticle - The article part of the ClonedSubcard.
* @param {Object} [{ eventElements = [], src = null }=[]]
* @memberof Card
*/
static _closeIndexCard(
context,
subcard,
clonedSubcard,
clonedArticle,
{ eventElements = [], src = null } = []
) {
let indexbox = context.querySelector('.mainview')
let padding = parseInt(this.css(indexbox, 'padding'))
let globalPreviewRect = Card._getGlobalRect(subcard)
let globalIndexCardRect = Card._getGlobalRect(indexbox)
let scale = {
x: globalPreviewRect.width / globalIndexCardRect.width,
y: globalPreviewRect.height / globalIndexCardRect.height
}
let titlebar = clonedSubcard.querySelector('.titlebar')
let desiredBorderBottomWidth = parseInt(window.getComputedStyle(titlebar).borderBottomWidth)
let localOrigin = Points.fromPageToNode(indexbox, Rect.getPosition(globalPreviewRect))
//logging
if (src) {
let strparts = src.split('/')
@ -1442,15 +1526,14 @@ export default class Card {
this._subcardChanged(context, true)
this._enableCardCloseButton(context)
let previewTitlebar = card.querySelector('.titlebar')
let previewTitlebar = subcard.querySelector('.titlebar')
let titlebarStyle = window.getComputedStyle(previewTitlebar)
let titlebar = clone.querySelector('.titlebar')
TweenLite.to(titlebar, this.animation.articleTransition, {
height: parseInt(titlebarStyle.height)
})
TweenLite.to(articleClone, this.animation.articleTransition / 2, {
TweenLite.to(clonedArticle, this.animation.articleTransition / 2, {
autoAlpha: 0
})
@ -1465,24 +1548,25 @@ export default class Card {
})
}
TweenLite.set(card, { autoAlpha: 1, css: { maxWidth } })
TweenLite.to(clone, this.animation.articleTransition, {
let maxWidth = this.css(subcard, 'max-width')
TweenLite.set(subcard, { autoAlpha: 1, css: { maxWidth } })
TweenLite.to(clonedSubcard, this.animation.articleTransition, {
x: localOrigin.x - padding,
y: localOrigin.y - padding,
scaleX,
scaleY,
ease: ExpoScaleEase.config(1, scaleX),
rotation: angle,
scaleX: scale.x,
scaleY: scale.y,
ease: ExpoScaleEase.config(1, scale.x),
// rotation: angle,
onComplete: () => {
// article.remove()
TweenLite.to(clone, this.animation.fade, {
TweenLite.to(clonedSubcard, this.animation.fade, {
//delay: 0.2,
autoAlpha: 0,
onComplete: () => {
if (editable) {
if (Card.isEditable()) {
mainController.popController()
}
clone.remove()
clonedSubcard.remove()
}
})
},
@ -1508,7 +1592,7 @@ export default class Card {
* Tests if jQuery is properly included in the project.
* Otherwise specific features may not work correctly (e.g. hyphenation)
*/
_jQueryIsPresent() {
static _isJQueryPresent() {
let jQueryInitialized = typeof $ != 'undefined'
if (!jQueryInitialized) console.error('No jQuery is provided. Specific features may fail.')
return jQueryInitialized
@ -1542,7 +1626,7 @@ export default class Card {
* @memberof Card
*/
static openIndexCard(event, src) {
//console.log("openIndexCard", src)
console.log('openIndexCard', src)
/*
* Called by the expandIndexCard(...)
*/
@ -1593,9 +1677,7 @@ export default class Card {
}
static _enableCardCloseButton(context) {
//console.log("ENABLE")
let btn = this._selectCardCloseButton(context)
//console.log(btn)
btn.classList.remove('disabled')
}
@ -1706,11 +1788,9 @@ export default class Card {
* @memberof Card
*/
static postProcessResponseText(context, htmlString) {
console.error('RUN POSTPROCESS')
let editable = this.isEditable()
htmlString = this._adjustRelativeLinks(htmlString)
//console.log(htmlString)
let parser = new DOMParser()
let html = parser.parseFromString(htmlString, 'text/html')
if (!editable) {
@ -1835,7 +1915,7 @@ export default class Card {
this._setPopupSource(popup, source)
context.popup = popup
if (this.constructor._jQueryIsPresent()) {
if (this._isJQueryPresent()) {
//jquery hyphenate below
console.log('hyphenated popup:', $('span').hyphenate('de'))
}
@ -1908,6 +1988,14 @@ export default class Card {
context.subcard = null
}
static _subcardChanged(context, closed = false) {
for (let [key, module] of Object.entries(context.module)) {
if (module.subcardChanged) {
module.subcardChanged(closed)
}
}
}
static incrementZIndex(context) {
if (!context.zIndex) context.zIndex = 0
context.zIndex++

View File

@ -2,6 +2,7 @@ export var CardPlugin = CardPlugin || {}
export class CardPluginBase {
apply(context) {
this.context = context
if (this.verify(context)) {
this.append(context)
console.log('Plugin ' + this.name + ' was verified successfully.')
@ -91,6 +92,14 @@ export class CardPluginBase {
}
return requirements
}
/**
* Called when the card is removed.
* Can be used to cleanup the plugin.
*
* @memberof CardPluginBase
*/
remove() {}
}
CardPlugin.LightBox = class LightBox extends CardPluginBase {
@ -392,7 +401,10 @@ CardPlugin.Speech = class SpeechPlugin extends CardPluginBase {
this.parentSelector = parentSelector
this.interactionType = interactionType
// We directly overwriting the function with a version that has a binded
// reference to itself. Doing so provides an easy and reliable way to remove
// the event listener using this function. - SO
this._domWasChanged = this._domWasChanged.bind(this)
/*
Speech doesn't stop when page is navigated.
@ -427,7 +439,7 @@ get cardActive() {
_updateText(ignoreSubcard = false) {
let node = this.context
let subcard = this.context.querySelector('.mainview > .subcard')
let subcard = node.querySelector('.mainview > .subcard')
if (ignoreSubcard) {
if (subcard != null) {
@ -485,6 +497,7 @@ get require() {
remove() {
this.button = null
this.context.removeEventListener('DOMNodeRemoved', this._domWasChanged)
super.remove()
}
@ -498,16 +511,17 @@ append(context) {
this.speak()
})
this.context.addEventListener('DOMNodeRemoved', event => {
if (
context.addEventListener('DOMNodeRemoved', this._domWasChanged)
}
_domWasChanged(event) {
if (this.context == null) this._stop()
else if (
this.context['lastSpeechNode'] == window.speechSynthesis['speechPluginNode'] &&
event.target == this.context
) {
this._stop()
}
})
ScatterCard
}
_isSameNode(node) {
@ -587,5 +601,4 @@ _activateButton() {
_deactivateButton() {
if (this.button) this.button.classList.remove('active')
}
}

View File

@ -18,6 +18,17 @@ export default class ScatterCard extends Card {
* @memberof ScatterCard
*/
static setup(context, htmlString, { basePath = './', modules = [] } = {}) {
if (typeof context.scatter == 'undefined') {
console.error(
"You need to wrap the context inside a DOMScatter before executing the ScatterCard's setup function."
)
}
/**
* This is required for the callback functions to work properly
*/
window.ScatterCard = ScatterCard
context.classList.add('info-card')
this.relativePath = basePath
@ -38,29 +49,6 @@ export default class ScatterCard extends Card {
return context
}
/**
* Appends a close listener to the scatter element.
*
* @static
* @param {*} element
* @param {*} callback
* @memberof Card
*/
static addOnCloseListener(element, callback) {
if (callback) {
element.onClose = callback
}
}
/**
* Removes the close listener from a card element.
*
* @static
* @param {HTMLElement} element - Context of the Card.
* @memberof ScatterCard
*/
static removeOnCloseListener(element) {
element.onClose = null
}
/**
* Creates a scatter for the card and applies the card to it,
*
@ -95,15 +83,19 @@ export default class ScatterCard extends Card {
* of the scatter.
*/
static close(context) {
Card.close(context)
if (context['scatter']) {
console.error('CLOSED CARD')
context.scatter.close()
} else {
if (typeof context.scatter != 'undefined') context.scatter.close()
else {
console.error('Expected a scatter element to close!', this)
}
// Card.close(context)
// if (context['scatter']) {
// console.error('CLOSED CARD')
// context.scatter.close()
// } else {
// console.error('Expected a scatter element to close!', this)
// }
}
/**
@ -116,7 +108,6 @@ export default class ScatterCard extends Card {
static remove(context) {
if (context['scatter']) {
context.scatter = null
console.error('REMOVED CARD')
} else {
console.error('Expected a scatter element to remove!', this)
}
@ -135,7 +126,7 @@ export default class ScatterCard extends Card {
* @returns
* @memberof CardScatter
*/
static loadAndCreateScatterCard(scatterContainer, item, { basePath = '../', modules = [], onClose = null } = {}) {
static loadAndCreateScatterCard(scatterContainer, item, { basePath = '../', modules = [] } = {}) {
console.log(basePath)
return new Promise((resolve, reject) => {
let url = basePath + '/' + item + '/index.html'
@ -147,7 +138,6 @@ export default class ScatterCard extends Card {
basePath,
modules
})
if (onClose) this.addOnCloseListener(element, onClose)
resolve(element)
})
.catch(e => reject(e))

View File

@ -64,6 +64,8 @@
})
})
createCard()
function createCard() {
const path = './example/01/index.html'