diff --git a/.gitignore b/.gitignore index 8ff4db4..7e62a17 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ +# Test HTML + +tests/test.html + +# Stupid Mac Files + +.DS_Store + # Logs logs *.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 91eec14..4edc2b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## v2.5.0 + +### New + +- Added `fireOnEmpty` modifier, which allow you to choose individual inputs you want to have fireOnEmpty +- Added `cancelOnEmpty` modifier which cancels the debounce all together if the input value is empty + +### Improved + +- Drastic code cleanup + +### Fixed + +- Bug with `fireOnEmpty` where debounce function would fire twice even when input was empty + ## v2.4.0 ### New diff --git a/README.md b/README.md index 605ee7c..ca7ed73 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ npm i vue-debounce - `lock` : Used to lock the debounce and prevent the enter key from triggering the function when pressed - Example: `v-debounce:400ms.lock="cb"` - `unlock` : Used to unlock the enter key on a debounced input, useful if you want to use the `lock` option and only want a few debounced inputs unlocked +- `fireOnEmpty` : Use to signify that when that specific input is emptied, you want the function to fire right away +- `cancelOnEmpty` : Use this to specify that when the input is emptied you **DO NOT** want your debounced function to trigger at all ## Options diff --git a/dist/vue-debounce.min.js b/dist/vue-debounce.min.js index ff37d6f..c8292e2 100644 --- a/dist/vue-debounce.min.js +++ b/dist/vue-debounce.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).vueDebounce={})}(this,function(e){"use strict";function v(n,e){function t(){for(var e=[],t=arguments.length;t--;)e[t]=arguments[t];clearTimeout(r),(r=setTimeout(function(){r=null,n.apply(void 0,e)},a))||n.apply(void 0,e)}var o,i,u,r=null,a="number"==typeof e?e:(o=String(e).split(/(ms|s)/i),i=o[0],void 0===(u=o[1])&&(u="ms"),Number(i)*{ms:1,s:1e3}[u]);return t.cancel=function(){clearTimeout(r),r=null},t}function s(e,t){var n=(e.getNamedItem("debounce-events")||{}).value;void 0===n&&(n=!1);function o(e){return e.map(function(e){return e.toLowerCase()})}return n?o(n.split(",")):Array.isArray(t)?o(t):[t]}var t={install:function(e,t){void 0===t&&(t={});var c=t.lock,l=t.listenTo;void 0===l&&(l="keyup");var f=t.defaultTime;void 0===f&&(f="300ms");var d=t.fireOnEmpty;void 0===d&&(d=!1),e.directive("debounce",{bind:function(t,e){var n=e.value,o=e.arg,i=e.modifiers,u=s(t.attributes,l),r=v(function(e){n(e.target.value,e)},o||f);function a(e){var t=!i.lock&&!c||i.unlock;("Enter"===e.key&&t||!e.target.value&&d)&&(r.cancel(),n(e.target.value,e)),"Enter"!==e.key&&r(e)}u.forEach(function(e){t.addEventListener(e,a)})}})}};e.debounce=v,e.default=t,Object.defineProperty(e,"__esModule",{value:!0})}); +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e=e||self).vueDebounce={})}(this,function(e){"use strict";function m(t,e){function n(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];clearTimeout(u),(u=setTimeout(function(){u=null,t.apply(void 0,e)},a))||t.apply(void 0,e)}var o,i,r,u=null,a="number"==typeof e?e:(o=String(e).split(/(ms|s)/i),i=o[0],void 0===(r=o[1])&&(r="ms"),Number(i)*{ms:1,s:1e3}[r]);return n.cancel=function(){clearTimeout(u),u=null},n}function i(e){return e.map(function(e){return e.toLowerCase()})}function p(e,n){var t,o=(e.getNamedItem("debounce-events")||{}).value;return void 0===o&&(o=!1),i(o?o.split(","):(t=n,Array.isArray(t)?t:null==t?[]:[t]))}function y(e){return""===e}var n={install:function(e,n){void 0===n&&(n={});var u=n.lock;void 0===u&&(u=!1);var f=n.listenTo;void 0===f&&(f="keyup");var v=n.defaultTime;void 0===v&&(v="300ms");var d=n.fireOnEmpty;void 0===d&&(d=!1);var s=n.cancelOnEmpty;void 0===s&&(s=!1),e.directive("debounce",{bind:function(n,e){var a=e.value,t=e.arg;void 0===t&&(t=v);var o=e.modifiers,c=Object.assign({fireonempty:d,cancelonempty:s,lock:u},o),i=p(n.attributes,f),l=m(function(e){a(e.target.value,e)},t);function r(e){var n,t,o,i,r,u;r=e.target.value,u=c,y(r)&&u.cancelonempty?l.cancel():(o=e.key,i=c,"Enter"===o&&(!i.lock||i.unlock)||(n=e.target.value,t=c,y(n)&&t.fireonempty)?(l.cancel(),a(e.target.value,e)):l(e))}i.forEach(function(e){n.addEventListener(e,r)})}})}};e.debounce=m,e.default=n,Object.defineProperty(e,"__esModule",{value:!0})}); diff --git a/package.json b/package.json index 4c1965e..1579c45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-debounce", - "version": "2.4.0", + "version": "2.5.0", "description": "A simple vue directive for debounce", "main": "dist/vue-debounce.min.js", "types": "types/index.d.ts", @@ -39,7 +39,7 @@ "devDependencies": { "chokidar": "3.3.1", "esm": "3.2.25", - "rollup": "1.29.0", + "rollup": "1.31.1", "rollup-plugin-buble": "0.19.8", "rollup-plugin-uglify": "6.0.4", "standard": "14.3.1", diff --git a/src/index.js b/src/index.js index fe65bf9..9304583 100644 --- a/src/index.js +++ b/src/index.js @@ -1,38 +1,87 @@ import debounce from './debounce' +// Helper Functions +/** + * Maps through an array of strings and lowercases all of them + * @param {Array} list an array of strings to map through + */ +function toLowerMap (list) { + return list.map(x => x.toLowerCase()) +} + +/** + * Takes in a value and ensures its wrapped within an array + * @param {Any} value The value to ensure is an array + */ +function ensureArray (value) { + if (Array.isArray(value)) { + return value + } + + if (value == null) { + return [] + } + + return [value] +} + // Figures out the event we are using with the bound element -function figureOutEvent (attrs, listenTo) { +function mapOutListeningEvents (attrs, listenTo) { const { value = false } = attrs.getNamedItem('debounce-events') || {} - const toLowerMap = list => list.map(x => x.toLowerCase()) // If they set an events attribute that overwrites everything if (value) { return toLowerMap(value.split(',')) } - return Array.isArray(listenTo) ? toLowerMap(listenTo) : [listenTo] + return toLowerMap(ensureArray(listenTo)) +} + +function isEmpty (str) { + return str === '' +} + +function isCanceled (inputValue, modifiers) { + return isEmpty(inputValue) && modifiers.cancelonempty +} + +function isLocked (key, modifiers) { + return key === 'Enter' && (!modifiers.lock || modifiers.unlock) +} + +function shouldFireOnEmpty (inputValue, modifiers) { + return isEmpty(inputValue) && modifiers.fireonempty } export { debounce } export default { - install (Vue, { lock, listenTo = 'keyup', defaultTime = '300ms', fireOnEmpty = false } = {}) { + install (Vue, { + lock = false, + listenTo = 'keyup', + defaultTime = '300ms', + fireOnEmpty = false, + cancelOnEmpty = false + } = {}) { Vue.directive('debounce', { - bind (el, { value, arg, modifiers }) { - const listener = figureOutEvent(el.attributes, listenTo) + bind (el, { + value: debouncedFn, + arg: timer = defaultTime, + modifiers + }) { + const combinedRules = Object.assign({ fireonempty: fireOnEmpty, cancelonempty: cancelOnEmpty, lock }, modifiers) + const listener = mapOutListeningEvents(el.attributes, listenTo) const fn = debounce(e => { - value(e.target.value, e) - }, arg || defaultTime) + debouncedFn(e.target.value, e) + }, timer) function handler (event) { - const isUnlocked = (!modifiers.lock && !lock) || modifiers.unlock - - if ((event.key === 'Enter' && isUnlocked) || (!event.target.value && fireOnEmpty)) { + if (isCanceled(event.target.value, combinedRules)) { fn.cancel() - value(event.target.value, event) - } - - if (event.key !== 'Enter') { + } else if (isLocked(event.key, combinedRules) || shouldFireOnEmpty(event.target.value, combinedRules)) { + fn.cancel() + debouncedFn(event.target.value, event) + } else { fn(event) } } diff --git a/tests/test.html b/tests/test.html deleted file mode 100644 index 6a30dcd..0000000 --- a/tests/test.html +++ /dev/null @@ -1,51 +0,0 @@ - - - -
- -