"use strict";
/**
* Adds the `hover` event as a DOM-event to event-dom. more about DOM-events:
* http://www.smashingmagazine.com/2013/11/12/an-introduction-to-dom-events/
*
* Should be called using the provided `mergeInto`-method like this:
*
*
* <i>Copyright (c) 2014 ITSA - https://github.com/itsa</i>
* New BSD License - http://choosealicense.com/licenses/bsd-3-clause/
*
* @example
* Event = require('event-dom/hover.js')(window);
*
* or
*
* @example
* Event = require('event-dom')(window);
* require('event-dom/event-hover.js')(window);
*
* @module event
* @submodule event-hover
* @class Event
* @since 0.0.2
*/
require('vdom');
require('js-ext/lib/object.js');
var NAME = '[event-valuechange]: ',
createHashMap = require('js-ext/extra/hashmap.js').createMap,
VALUE = 'value',
DATA_KEY = 'valueChange',
UTILS = require('utils'),
/**
* Interval (in milliseconds) at which to poll for changes to the value of an
* element with one or more `valuechange` subscribers, because of a `right-click paste`
* which cannot be determined by the event-system
*
* @property POLL_INTERVAL
* @type Number
* @default 250
* @static
**/
POLL_INTERVAL = 250;
module.exports = function (window) {
window._ITSAmodules || Object.protectedProp(window, '_ITSAmodules', createHashMap());
if (window._ITSAmodules.EventValueChange) {
return window._ITSAmodules.EventValueChange; // EventValueChange was already created
}
var Event = require('../event-dom.js')(window),
DOCUMENT = window.document,
subscriberBlur,
subscriberFocus,
subscriberRemoval,
/*
* Checks if the HtmlElement is editable.
*
* @method editableNode
* @param node {HtmlElement}
* @private
* @return {Boolean} whether the HtmlElement is editable.
* @since 0.0.1
*/
editableNode = function(node) {
var editable;
if (node===DOCUMENT) {
return false;
}
console.log(NAME, 'editableNodes '+DOCUMENT.test(node, 'input, textarea, select') || ((editable=node.getAttr('contenteditable')) && (editable!=='false')));
return DOCUMENT.test(node, 'input, textarea, select') || ((editable=node.getAttr('contenteditable')) && (editable!=='false'));
},
/*
* Gets invokes when the HtmlElement gets focus. Initializes a `keypress` and `click`/'press' eventlisteners.
*
* @method startFocus
* @param e {Object} eventobject
* @private
* @since 0.0.1
*/
startFocus = function(e) {
console.log(NAME, 'startFocus');
var node = e.target,
editable, valueChangeData;
if (!editableNode(node)) {
return;
}
// first backup the current value:
editable = ((editable=node.getAttr('contenteditable')) && (editable!=='false'));
valueChangeData = node.getData(DATA_KEY);
if (!valueChangeData) {
valueChangeData = {
editable : editable
};
node.setData(DATA_KEY, valueChangeData);
}
valueChangeData.prevVal = editable ? node.innerHTML : node[VALUE];
// both next eventlisteners will detach inside their subscriber:
subscriberBlur = Event.after('blur', endFocus);
subscriberRemoval = Event.after(
'noderemove',
endFocus,
function(e2) {
return (e2.target===node);
}
);
startPolling(e);
},
/*
* Removes the `focus` and `blur` events and ends the polling - if running. Because there are no subscribers anymore.
*
* @method endFocus
* @private
* @since 0.0.1
*/
endFocus = function(e) {
console.log(NAME, 'endFocus');
stopPolling(e.target);
// because we could come here by 2 different events,
// we need to detach them both
subscriberBlur.detach();
subscriberRemoval.detach();
},
/*
* Creates the `focus` and `blur` events. Also invokes `startFocus` to do inititalization.
*
* @method setupValueChange
* @private
* @since 0.0.2
*/
setupValueChange = function() {
console.log(NAME, 'setupValueChange');
// create only after subscribing to the `hover`-event
subscriberFocus = Event.after('focus', startFocus);
startFocus({target: DOCUMENT.activeElement});
},
/*
* Starts polling in case of mouseclicks.
*
* @method startPolling
* @private
* @since 0.0.1
*/
startPolling = function(e) {
var node = e.target,
valueChangeData;
if (!editableNode(node)) {
return;
}
console.log(NAME, 'startPolling');
valueChangeData = node.getData(DATA_KEY);
// cancel previous timer: we don't want multiple timers:
valueChangeData._pollTimer && valueChangeData._pollTimer.cancel();
// setup a new timer:
valueChangeData._pollTimer = UTILS.later(checkChanged.bind(null, e), POLL_INTERVAL, true);
},
/*
* Stops polling on the specific HtmlElement
*
* @method stopPolling
* @param node {HtmlElement} the HtmlElement that should stop polling.
* @private
* @since 0.0.1
*/
stopPolling = function(node) {
console.log(NAME, 'stopPolling');
var valueChangeData;
if (node && node.getData) {
valueChangeData = node.getData(DATA_KEY);
valueChangeData && valueChangeData._pollTimer && valueChangeData._pollTimer.cancel();
}
},
/*
* Checks e.target if its value has changed. If so, it will fire the `valuechange`-event.
*
* @method checkChanged
* @param e {Object} eventobject
* @private
* @since 0.0.1
*/
checkChanged = function(e) {
console.log(NAME, 'checkChanged');
var node = e.target;
// because of delegating all matched HtmlElements come along: only check the node that has focus:
if (DOCUMENT.activeElement!==node) {
return;
}
var prevData = node.getData(DATA_KEY),
editable = ((editable=node.getAttr('contenteditable')) && (editable!=='false')),
currentData = editable ? node.innerHTML : node[VALUE];
if (currentData!==prevData.prevVal) {
console.log(NAME, 'checkChanged --> value has been changed');
DOCUMENT._emitVC(node, currentData);
prevData.prevVal = currentData;
}
},
/*
* Removes the `focus` and `blur` events and ends the polling - if running. Because there are no subscribers anymore.
*
* @method teardownValueChange
* @private
* @since 0.0.1
*/
teardownValueChange = function() {
// check if there aren't any subscribers anymore.
// in that case, we detach the `mouseover` lister because we don't want to
// loose performance.
if (!Event._subs['UI:valuechange']) {
console.log(NAME, 'teardownValueChange: stop setting up blur and focus-event');
subscriberBlur && subscriberBlur.detach();
subscriberRemoval && subscriberRemoval.detach();
subscriberFocus.detach();
// also stop any possible action/listeners to a current element:
endFocus({target: DOCUMENT.activeElement});
// reinit notifier, because it is a one-time notifier:
Event.notify('UI:valuechange', setupValueChange, Event, true);
}
};
Event.defineEvent('UI:valuechange').unHaltable();
Event.notify('UI:valuechange', setupValueChange, Event, true);
Event.notifyDetach('UI:valuechange', teardownValueChange, Event);
/*
* Emits the `valuechange`-event on the specified node. Also adds e.value with the new value.
*
* @method _emitVC
* @param node {HtmlElement} the HtmlElement that fires the event
* @param value {String} the new value
* @private
* @since 0.0.1
*/
DOCUMENT._emitVC = function(node, value) {
console.log(NAME, 'document._emitVC');
var e = {
value: value
};
/**
* @event valuechange
* @param e.value {String} new value
* @param e.sourceTarget {Element} Element whare the valuechange occured
*/
Event.emit(node, 'UI:valuechange', e);
};
window._ITSAmodules.EventValueChange = Event;
return Event;
};