diff --git a/README.md b/README.md index 40266d9..e5a93b8 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ Observe.js [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Observe.js%2C%20a%20free%20JavaScript%observe%builder&url=https://github.com/williamtroup/Observe.js&hashtags=javascript,html,observe) -[![npm](https://img.shields.io/badge/npmjs-v0.1.0-blue)](https://www.npmjs.com/package/jobserve.js) -[![nuget](https://img.shields.io/badge/nuget-v0.1.0-purple)](https://www.nuget.org/packages/jObserve.js/) +[![npm](https://img.shields.io/badge/npmjs-v0.2.0-blue)](https://www.npmjs.com/package/jobserve.js) +[![nuget](https://img.shields.io/badge/nuget-v0.2.0-purple)](https://www.nuget.org/packages/jObserve.js/) [![license](https://img.shields.io/badge/license-MIT-green)](https://github.com/williamtroup/Observe.js/blob/main/LICENSE.txt) [![discussions Welcome](https://img.shields.io/badge/discussions-Welcome-red)](https://github.com/williamtroup/Observe.js/discussions) [![coded by William Troup](https://img.shields.io/badge/coded_by-William_Troup-yellow)](https://github.com/williamtroup) >

A lightweight, and easy-to-use, JavaScript library for observing any kind of JS object, or HTML DOM element, to detect changes!

->

v0.1.0

+>

v0.2.0



@@ -18,9 +18,11 @@ Observe.js - Zero-dependencies and extremely lightweight! - JS Object, and HTML DOM Element watching! +- Cancellation support! - Full API available via public functions. -- Full configurable! -- Custom triggers for actions (when changes are detected, etc). +- Fully configurable! +- Fully configurable per watch! +- Custom triggers for actions (when changes are detected, on cancellation, etc).

diff --git a/README_NUGET.md b/README_NUGET.md index d5642fe..2b6b335 100644 --- a/README_NUGET.md +++ b/README_NUGET.md @@ -1,8 +1,8 @@ -# Observe.js v0.1.0 +# Observe.js v0.2.0 [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Observe.js%2C%20a%20free%20JavaScript%observe%builder&url=https://github.com/williamtroup/Observe.js&hashtags=javascript,html,observe) -[![npm](https://img.shields.io/badge/npmjs-v0.1.0-blue)](https://www.npmjs.com/package/jobserve.js) -[![nuget](https://img.shields.io/badge/nuget-v0.1.0-purple)](https://www.nuget.org/packages/jObserve.js/) +[![npm](https://img.shields.io/badge/npmjs-v0.2.0-blue)](https://www.npmjs.com/package/jobserve.js) +[![nuget](https://img.shields.io/badge/nuget-v0.2.0-purple)](https://www.nuget.org/packages/jObserve.js/) [![license](https://img.shields.io/badge/license-MIT-green)](https://github.com/williamtroup/Observe.js/blob/main/LICENSE.txt) [![discussions Welcome](https://img.shields.io/badge/discussions-Welcome-red)](https://github.com/williamtroup/Observe.js/discussions) [![coded by William Troup](https://img.shields.io/badge/coded_by-William_Troup-yellow)](https://github.com/williamtroup) @@ -14,9 +14,11 @@ - Zero-dependencies and extremely lightweight! - JS Object, and HTML DOM Element watching! +- Cancellation support! - Full API available via public functions. -- Full configurable! -- Custom triggers for actions (when changes are detected, etc). +- Fully configurable! +- Fully configurable per watch! +- Custom triggers for actions (when changes are detected, on cancellation, etc). ## What browsers are supported? diff --git a/dist/observe.js b/dist/observe.js index c593292..355046b 100644 --- a/dist/observe.js +++ b/dist/observe.js @@ -1,4 +1,4 @@ -/*! Observe.js v0.1.0 | (c) Bunoon | MIT License */ +/*! Observe.js v0.2.0 | (c) Bunoon | MIT License */ (function() { function collectDOMObjects() { var tagTypes = _configuration.domElementTypes; @@ -23,11 +23,11 @@ if (isDefinedString(bindingOptionsData)) { var bindingOptions = getObjectFromString(bindingOptionsData); if (bindingOptions.parsed && isDefinedObject(bindingOptions.result)) { - bindingOptions = getObserveOptions(bindingOptions.result); + bindingOptions = getWatchOptions(bindingOptions.result); if (!isDefinedString(element.id)) { element.id = newGuid(); } - createObservableObject(element, bindingOptions, element.id); + createWatch(element, bindingOptions, element.id); } else { if (!_configuration.safeMode) { console.error("The attribute '" + _attribute_Name_Options + "' is not a valid object."); @@ -43,62 +43,84 @@ } return result; } - function createObservableObject(object, options, domElementId) { + function createWatch(object, options, domElementId) { var storageId = null; if (isDefinedObject(object)) { storageId = newGuid(); - var observeOptions = getObserveOptions(options); - _observables[storageId] = {}; - _observables[storageId].options = observeOptions; - _observables[storageId].domElementId = domElementId; + var watchOptions = getWatchOptions(options); + var watch = {}; + watch.options = watchOptions; + watch.domElementId = domElementId; + watch.totalChanges = 0; if (isDefinedString(domElementId)) { var domElement = _parameter_Document.getElementById(domElementId); if (isDefined(domElement)) { - _observables[storageId].cachedObject = domElement.innerHTML; - _observables[storageId].originalObject = domElement.innerHTML; + watch.cachedObject = domElement.outerHTML; + watch.originalObject = domElement.outerHTML; } } else { - _observables[storageId].cachedObject = JSON.stringify(object); - _observables[storageId].originalObject = object; + watch.cachedObject = JSON.stringify(object); + watch.originalObject = object; } - _observables[storageId].timer = setInterval(function() { + watch.timer = setInterval(function() { var currentDateTime = new Date(); - observeObject(storageId); - if (isDefinedDate(observeOptions.expires) && currentDateTime > observeOptions.expires) { - clearTimeout(_observables[storageId].timer); - delete _observables[storageId]; + if (!isDefinedDate(watchOptions.starts) || currentDateTime >= watchOptions.starts) { + watchObjectForChanges(storageId); + if (isDefinedDate(watchOptions.expires) && currentDateTime >= watchOptions.expires) { + cancelWatchObject(storageId); + } } - }, observeOptions.observeTimeout); + }, watchOptions.timeout); + _watches[storageId] = watch; } return storageId; } - function observeObject(storageId) { - var isDomElement = isDefinedString(_observables[storageId].domElementId); - if (isDomElement) { - var domElement = _parameter_Document.getElementById(_observables[storageId].domElementId); - if (isDefined(domElement)) { - _observables[storageId].originalObject = domElement.innerHTML; - } - } - var cachedObject = _observables[storageId].cachedObject; - var originalObject = _observables[storageId].originalObject; - var originalObjectJson = !isDomElement ? JSON.stringify(originalObject) : originalObject; - if (cachedObject !== originalObjectJson) { - _observables[storageId].cachedObject = originalObjectJson; - var options = _observables[storageId].options; + function watchObjectForChanges(storageId) { + if (_watches.hasOwnProperty(storageId)) { + var watch = _watches[storageId]; + var isDomElement = isDefinedString(watch.domElementId); + var domElement = null; if (isDomElement) { - fireCustomTrigger(options.onChange, cachedObject, originalObjectJson); - } else { - var oldValue = getObjectFromString(cachedObject).result; - var newValue = getObjectFromString(originalObjectJson).result; - fireCustomTrigger(options.onChange, oldValue, newValue); - if (isDefinedFunction(options.onPropertyChange) && !isDefinedArray(oldValue)) { - compareObservableObjectProperties(oldValue, newValue, options); + domElement = _parameter_Document.getElementById(watch.domElementId); + if (isDefined(domElement)) { + watch.originalObject = domElement.outerHTML; + } + } + var cachedObject = watch.cachedObject; + var originalObject = watch.originalObject; + var originalObjectJson = !isDomElement ? JSON.stringify(originalObject) : originalObject; + if (cachedObject !== originalObjectJson) { + var watchOptions = watch.options; + if (watchOptions.reset) { + if (isDomElement) { + domElement.outerHTML = watch.cachedObject; + } else { + watch.originalObject = getObjectFromString(cachedObject).result; + } + } else { + watch.cachedObject = originalObjectJson; + } + if (isDomElement) { + fireCustomTrigger(watchOptions.onChange, cachedObject, originalObjectJson); + } else { + var oldValue = getObjectFromString(cachedObject).result; + var newValue = getObjectFromString(originalObjectJson).result; + fireCustomTrigger(watchOptions.onChange, oldValue, newValue); + if (isDefinedFunction(watchOptions.onPropertyChange) && !isDefinedArray(oldValue)) { + compareWatchObjectProperties(oldValue, newValue, watchOptions); + } + } + if (watchOptions.cancelOnChange) { + cancelWatchObject(storageId); + } + watch.totalChanges++; + if (watchOptions.maximumChangesBeforeCanceling > 0 && watch.totalChanges >= watchOptions.maximumChangesBeforeCanceling) { + cancelWatchObject(storageId); } } } } - function compareObservableObjectProperties(oldObject, newObject, options) { + function compareWatchObjectProperties(oldObject, newObject, options) { var propertyName; for (propertyName in oldObject) { if (oldObject.hasOwnProperty(propertyName)) { @@ -108,7 +130,7 @@ propertyNewValue = newObject[propertyName]; } if (isDefinedObject(propertyOldValue) && isDefinedObject(propertyNewValue)) { - compareObservableObjectProperties(propertyOldValue, propertyNewValue, options); + compareWatchObjectProperties(propertyOldValue, propertyNewValue, options); } else { if (JSON.stringify(propertyOldValue) !== JSON.stringify(propertyNewValue)) { fireCustomTrigger(options.onPropertyChange, propertyName, propertyOldValue, propertyNewValue); @@ -117,12 +139,29 @@ } } } - function getObserveOptions(newOptions) { + function cancelWatchObject(storageId) { + if (_watches.hasOwnProperty(storageId)) { + var watchOptions = _watches[storageId].options; + fireCustomTrigger(watchOptions.onCancel, storageId); + clearTimeout(_watches[storageId].timer); + delete _watches[storageId]; + } + } + function getWatchOptions(newOptions) { var options = !isDefinedObject(newOptions) ? {} : newOptions; - options.observeTimeout = getDefaultNumber(options.observeTimeout, 250); + options.timeout = getDefaultNumber(options.timeout, 250); + options.starts = getDefaultDate(options.starts, null); options.expires = getDefaultDate(options.expires, null); + options.reset = getDefaultBoolean(options.reset, false); + options.cancelOnChange = getDefaultBoolean(options.cancelOnChange, false); + options.maximumChangesBeforeCanceling = getDefaultNumber(options.maximumChangesBeforeCanceling, 0); + options = getWatchOptionsCustomTriggers(options); + return options; + } + function getWatchOptionsCustomTriggers(options) { options.onChange = getDefaultFunction(options.onChange, null); options.onPropertyChange = getDefaultFunction(options.onPropertyChange, null); + options.onCancel = getDefaultFunction(options.onCancel, null); return options; } function fireCustomTrigger(triggerFunction) { @@ -227,30 +266,40 @@ var _parameter_Document = null; var _parameter_Window = null; var _string = {empty:""}; - var _observables = {}; + var _watches = {}; var _configuration = {}; var _attribute_Name_Options = "data-observe-options"; this.watchObject = function(object, options) { - return createObservableObject(object, options); + return createWatch(object, options); }; this.cancelWatch = function(id) { var result = false; - if (_observables.hasOwnProperty(id)) { - clearTimeout(_observables[id].timer); - delete _observables[id]; + if (_watches.hasOwnProperty(id)) { + cancelWatchObject(id); result = true; + } else { + var storageId; + for (storageId in _watches) { + if (_watches.hasOwnProperty(storageId) && isDefinedString(_watches[storageId].domElementId) && _watches[storageId].domElementId === id) { + cancelWatchObject(storageId); + result = true; + break; + } + } } return result; }; - this.cancelDomElementWatch = function(elementId) { - var result = false; - var storageId; - for (storageId in _observables) { - if (_observables.hasOwnProperty(storageId) && isDefinedString(_observables[storageId].domElementId) && _observables[storageId].domElementId === elementId) { - clearTimeout(_observables[storageId].timer); - delete _observables[storageId]; - result = true; - break; + this.getWatch = function(id) { + var result = null; + if (_watches.hasOwnProperty(id)) { + result = _watches[id]; + } else { + var storageId; + for (storageId in _watches) { + if (_watches.hasOwnProperty(storageId) && isDefinedString(_watches[storageId].domElementId) && _watches[storageId].domElementId === id) { + result = _watches[storageId]; + break; + } } } return result; @@ -261,7 +310,7 @@ return this; }; this.getVersion = function() { - return "0.1.0"; + return "0.2.0"; }; (function(documentObject, windowObject) { _parameter_Document = documentObject; diff --git a/dist/observe.min.js b/dist/observe.min.js index 542abe1..5077f20 100644 --- a/dist/observe.min.js +++ b/dist/observe.min.js @@ -1,8 +1,9 @@ -/*! Observe.js v0.1.0 | (c) Bunoon | MIT License */ -(function(){function A(a,b,c){var e=null;if(q(a)){e=B();var g=C(b);d[e]={};d[e].options=g;d[e].domElementId=c;t(c)?(a=u.getElementById(c),n(a)&&(d[e].cachedObject=a.innerHTML,d[e].originalObject=a.innerHTML)):(d[e].cachedObject=JSON.stringify(a),d[e].originalObject=a);d[e].timer=setInterval(function(){var k=new Date,h=e,l=t(d[h].domElementId);if(l){var f=u.getElementById(d[h].domElementId);n(f)&&(d[h].originalObject=f.innerHTML)}f=d[h].cachedObject;var m=d[h].originalObject;m=l?m:JSON.stringify(m); -f!==m&&(d[h].cachedObject=m,h=d[h].options,l?w(h.onChange,f,m):(l=x(f).result,f=x(m).result,w(h.onChange,l,f),v(h.onPropertyChange)&&!D(l)&&E(l,f,h)));F(g.expires)&&k>g.expires&&(clearTimeout(d[e].timer),delete d[e])},g.observeTimeout)}return e}function E(a,b,c){for(var e in a)if(a.hasOwnProperty(e)){var g=a[e],k=null;b.hasOwnProperty(e)&&(k=b[e]);q(g)&&q(k)?E(g,k,c):JSON.stringify(g)!==JSON.stringify(k)&&w(c.onPropertyChange,e,g,k)}}function C(a){a=q(a)?a:{};var b=a.observeTimeout;var c=n(b)&&"number"=== -typeof b;a.observeTimeout=c?b:250;b=a.expires;b=F(b)?b:null;a.expires=b;b=a.onChange;b=v(b)?b:null;a.onChange=b;b=a.onPropertyChange;b=v(b)?b:null;a.onPropertyChange=b;return a}function w(a){v(a)&&a.apply(null,[].slice.call(arguments,1))}function B(){for(var a=[],b=0;32>b;b++){8!==b&&12!==b&&16!==b&&20!==b||a.push("-");var c=Math.floor(16*Math.random()).toString(16);a.push(c)}return a.join(y.empty)}function n(a){return null!==a&&void 0!==a&&a!==y.empty}function q(a){return n(a)&&"object"===typeof a} -function t(a){return n(a)&&"string"===typeof a}function v(a){return n(a)&&"function"===typeof a}function D(a){return q(a)&&a instanceof Array}function F(a){return q(a)&&a instanceof Date}function x(a){var b=!0,c=null;try{t(a)&&(c=JSON.parse(a))}catch(e){try{c=eval("("+a+")"),v(c)&&(c=c())}catch(g){b=H("Errors in object: "+e.message+", "+g.message),c=null}}return{parsed:b,result:c}}function H(a){var b=!0;p.safeMode||(console.error(a),b=!1);return b}function G(){var a=p,b=p.safeMode;var c=n(b)&&"boolean"=== -typeof b;a.safeMode=c?b:!0;a=p;c=p.domElementTypes;b=["*"];t(c)?(c=c.split(y.space),0===c.length&&(c=b)):c=D(c)?c:b;a.domElementTypes=c}var u=null,z=null,y={empty:""},d={},p={};this.watchObject=function(a,b){return A(a,b)};this.cancelWatch=function(a){var b=!1;d.hasOwnProperty(a)&&(clearTimeout(d[a].timer),delete d[a],b=!0);return b};this.cancelDomElementWatch=function(a){var b=!1,c;for(c in d)if(d.hasOwnProperty(c)&&t(d[c].domElementId)&&d[c].domElementId===a){clearTimeout(d[c].timer);delete d[c]; -b=!0;break}return b};this.setConfiguration=function(a){p=q(a)?a:{};G();return this};this.getVersion=function(){return"0.1.0"};(function(a,b){u=a;z=b;G();u.addEventListener("DOMContentLoaded",function(){for(var c=p.domElementTypes,e=c.length,g=0;g=e.starts){var u=g;if(d.hasOwnProperty(u)){var h=d[u],f=t(h.domElementId),m=null;f&&(m=v.getElementById(h.domElementId),p(m)&&(h.originalObject=m.outerHTML));var l=h.cachedObject, +n=h.originalObject,y=f?n:JSON.stringify(n);l!==y&&(n=h.options,n.reset?f?m.outerHTML=h.cachedObject:h.originalObject=z(l).result:h.cachedObject=y,f?A(n.onChange,l,y):(f=z(l).result,m=z(y).result,A(n.onChange,f,m),B(n.onPropertyChange)&&!J(f)&&K(f,m,n)),n.cancelOnChange&&w(u),h.totalChanges++,0=n.maximumChangesBeforeCanceling&&w(u))}x(e.expires)&&k>=e.expires&&w(g)}},e.timeout);d[g]=b}return g}function K(a,b,c){for(var g in a)if(a.hasOwnProperty(g)){var e= +a[g],k=null;b.hasOwnProperty(g)&&(k=b[g]);r(e)&&r(k)?K(e,k,c):JSON.stringify(e)!==JSON.stringify(k)&&A(c.onPropertyChange,g,e,k)}}function w(a){d.hasOwnProperty(a)&&(A(d[a].options.onCancel,a),clearTimeout(d[a].timer),delete d[a])}function I(a){var b=a=r(a)?a:{};var c=a.timeout;c=L(c)?c:250;b.timeout=c;b=a;c=a.starts;c=x(c)?c:null;b.starts=c;b=a;c=a.expires;c=x(c)?c:null;b.expires=c;a.reset=C(a.reset,!1);a.cancelOnChange=C(a.cancelOnChange,!1);b=a;c=a.maximumChangesBeforeCanceling;c=L(c)?c:0;b.maximumChangesBeforeCanceling= +c;a.onChange=D(a.onChange,null);a.onPropertyChange=D(a.onPropertyChange,null);a.onCancel=D(a.onCancel,null);return a}function A(a){B(a)&&a.apply(null,[].slice.call(arguments,1))}function H(){for(var a=[],b=0;32>b;b++){8!==b&&12!==b&&16!==b&&20!==b||a.push("-");var c=Math.floor(16*Math.random()).toString(16);a.push(c)}return a.join(E.empty)}function p(a){return null!==a&&void 0!==a&&a!==E.empty}function r(a){return p(a)&&"object"===typeof a}function t(a){return p(a)&&"string"===typeof a}function B(a){return p(a)&& +"function"===typeof a}function L(a){return p(a)&&"number"===typeof a}function J(a){return r(a)&&a instanceof Array}function x(a){return r(a)&&a instanceof Date}function C(a,b){return p(a)&&"boolean"===typeof a?a:b}function D(a,b){return B(a)?a:b}function z(a){var b=!0,c=null;try{t(a)&&(c=JSON.parse(a))}catch(g){try{c=eval("("+a+")"),B(c)&&(c=c())}catch(e){b=N("Errors in object: "+g.message+", "+e.message),c=null}}return{parsed:b,result:c}}function N(a){var b=!0;q.safeMode||(console.error(a),b=!1); +return b}function M(){q.safeMode=C(q.safeMode,!0);var a=q,b=q.domElementTypes,c=["*"];t(b)?(b=b.split(E.space),0===b.length&&(b=c)):b=J(b)?b:c;a.domElementTypes=b}var v=null,F=null,E={empty:""},d={},q={};this.watchObject=function(a,b){return G(a,b)};this.cancelWatch=function(a){var b=!1;if(d.hasOwnProperty(a))w(a),b=!0;else for(var c in d)if(d.hasOwnProperty(c)&&t(d[c].domElementId)&&d[c].domElementId===a){w(c);b=!0;break}return b};this.getWatch=function(a){var b=null;if(d.hasOwnProperty(a))b=d[a]; +else for(var c in d)if(d.hasOwnProperty(c)&&t(d[c].domElementId)&&d[c].domElementId===a){b=d[c];break}return b};this.setConfiguration=function(a){q=r(a)?a:{};M();return this};this.getVersion=function(){return"0.2.0"};(function(a,b){v=a;F=b;M();v.addEventListener("DOMContentLoaded",function(){for(var c=q.domElementTypes,g=c.length,e=0;e + ## Version 0.1.0: - Everything :) \ No newline at end of file diff --git a/docs/PUBLIC_FUNCTIONS.md b/docs/PUBLIC_FUNCTIONS.md index 60c475f..aba3f83 100644 --- a/docs/PUBLIC_FUNCTIONS.md +++ b/docs/PUBLIC_FUNCTIONS.md @@ -20,17 +20,17 @@ Adds an object that should be watched for changes. ### **cancelWatch( *id* )**: Cancels the watching of an object for changes.
-***Parameter: id***: '*string*' - The Id of the object being watched. +***Parameter: id***: '*string*' - The Id of the object being watched, or DOM element ID being watched.
***Returns***: '*boolean*' - States if the object being watched has been canceled.
-### **cancelDomElementWatch( *elementId* )**: -Cancels the watching of a DOM element object for changes. +### **getWatch( *id* )**: +Returns the properties for an active watch.
-***Parameter: elementId***: '*string*' - The Id of the DOM element object being watched. +***Parameter: id***: '*string*' - The Id of the object being watched, or DOM element ID being watched.
-***Returns***: '*boolean*' - States if the DOM element object being watched has been canceled. +***Returns***: '*Object*' - The watch properties for an object (null if not found).

diff --git a/docs/binding/options/CUSTOM_TRIGGERS.md b/docs/binding/options/CUSTOM_TRIGGERS.md index cfedaff..c61d4b0 100644 --- a/docs/binding/options/CUSTOM_TRIGGERS.md +++ b/docs/binding/options/CUSTOM_TRIGGERS.md @@ -23,6 +23,11 @@ Fires when a change has been detected in an object (states which property change
***Parameter:*** newValue: '*object*' - The new value. +### options.onCancel( *id* ): +Fires when a watch has been canceled. +
+***Parameter:*** id: '*string*' - The ID of the watch that has been canceled. +
diff --git a/docs/binding/options/OPTIONS.md b/docs/binding/options/OPTIONS.md index d6e18d1..7f931f2 100644 --- a/docs/binding/options/OPTIONS.md +++ b/docs/binding/options/OPTIONS.md @@ -10,8 +10,12 @@ Below is a list of all the options supported in the "data-observe-options" bindi | Type: | Name: | Description: | | --- | --- | --- | -| *number* | observeTimeout | States the delay that should be waited before checking the object for changes (defaults to 250 milliseconds). | +| *number* | timeout | States the delay that should be waited before checking the object for changes (defaults to 250 milliseconds). | +| *date* | starts | States a date/time when the watch should start (defaults to null). | | *date* | expires | States a date/time when the watch should expire (defaults to null). | +| *boolean* | reset | States if the original value of the object should be reset back to its original value when a change is detected (defaults to false). | +| *boolean* | cancelOnChange | States if the watch should be canceled when te first change is detected (defaults to false). | +| *number* | maximumChangesBeforeCanceling | States the total number of changes that are allowed before the watch is canceled (defaults to 0, which is off) |
@@ -19,7 +23,7 @@ Below is a list of all the options supported in the "data-observe-options" bindi
```markdown -
+
Your HTML.
``` @@ -33,7 +37,7 @@ Below is a list of all the options supported in the "data-observe-options" bindi ```markdown ``` \ No newline at end of file diff --git a/observe.js.nuspec b/observe.js.nuspec index 921bbca..108c71c 100644 --- a/observe.js.nuspec +++ b/observe.js.nuspec @@ -2,7 +2,7 @@ jObserve.js - 0.1.0 + 0.2.0 Observe.js A lightweight, and easy-to-use, JavaScript library for observing any kind of JS object, or HTML DOM element, to detect changes. William Troup diff --git a/package.json b/package.json index fb48759..a2b4c3c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "jobserve.js", "title": "Observe.js", "description": "A lightweight, and easy-to-use, JavaScript library for observing any kind of JS object, or HTML DOM element, to detect changes.", - "version": "0.1.0", + "version": "0.2.0", "main": "dist/observe.js", "author": { "name": "Bunoon" diff --git a/src/observe.js b/src/observe.js index c948c7f..68b3dd3 100644 --- a/src/observe.js +++ b/src/observe.js @@ -4,7 +4,7 @@ * A lightweight, and easy-to-use, JavaScript library for observing any kind of JS object, or HTML DOM element, to detect changes. * * @file observe.js - * @version v0.1.0 + * @version v0.2.0 * @author Bunoon * @license MIT License * @copyright Bunoon 2023 @@ -21,8 +21,8 @@ empty: "" }, - // Variables: Observables - _observables = {}, + // Variables: Watches + _watches = {}, // Variables: Configuration _configuration = {}, @@ -64,13 +64,13 @@ var bindingOptions = getObjectFromString( bindingOptionsData ); if ( bindingOptions.parsed && isDefinedObject( bindingOptions.result ) ) { - bindingOptions = getObserveOptions( bindingOptions.result ); + bindingOptions = getWatchOptions( bindingOptions.result ); if ( !isDefinedString( element.id ) ) { element.id = newGuid(); } - createObservableObject( element, bindingOptions, element.id ); + createWatch( element, bindingOptions, element.id ); } else { if ( !_configuration.safeMode ) { @@ -93,88 +93,115 @@ /* * ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - * Observable Object Creation / Handling + * Watch Object Creation / Handling * ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ - function createObservableObject( object, options, domElementId ) { + function createWatch( object, options, domElementId ) { var storageId = null; if ( isDefinedObject( object ) ) { storageId = newGuid(); - var observeOptions = getObserveOptions( options ); + var watchOptions = getWatchOptions( options ), + watch = {}; - _observables[ storageId ] = {}; - _observables[ storageId ].options = observeOptions; - _observables[ storageId ].domElementId = domElementId; + watch.options = watchOptions; + watch.domElementId = domElementId; + watch.totalChanges = 0; if ( isDefinedString( domElementId ) ) { var domElement = _parameter_Document.getElementById( domElementId ); if ( isDefined( domElement ) ) { - _observables[ storageId ].cachedObject = domElement.innerHTML; - _observables[ storageId ].originalObject = domElement.innerHTML; + watch.cachedObject = domElement.outerHTML; + watch.originalObject = domElement.outerHTML; } } else { - _observables[ storageId ].cachedObject = JSON.stringify( object ); - _observables[ storageId ].originalObject = object; + watch.cachedObject = JSON.stringify( object ); + watch.originalObject = object; } - _observables[ storageId ].timer = setInterval( function() { + watch.timer = setInterval( function() { var currentDateTime = new Date(); - observeObject( storageId ); + if ( !isDefinedDate( watchOptions.starts ) || currentDateTime >= watchOptions.starts ) { + watchObjectForChanges( storageId ); - if ( isDefinedDate( observeOptions.expires ) && currentDateTime > observeOptions.expires ) { - clearTimeout( _observables[ storageId ].timer ); - delete _observables[ storageId ]; + if ( isDefinedDate( watchOptions.expires ) && currentDateTime >= watchOptions.expires ) { + cancelWatchObject( storageId ); + } } - }, observeOptions.observeTimeout ); + }, watchOptions.timeout ); + + _watches[ storageId ] = watch; } return storageId; } - function observeObject( storageId ) { - var isDomElement = isDefinedString( _observables[ storageId ].domElementId ); + function watchObjectForChanges( storageId ) { + if ( _watches.hasOwnProperty( storageId ) ) { + var watch = _watches[ storageId ], + isDomElement = isDefinedString( watch.domElementId ), + domElement = null; - if ( isDomElement ) { - var domElement = _parameter_Document.getElementById( _observables[ storageId ].domElementId ); + if ( isDomElement ) { + domElement = _parameter_Document.getElementById( watch.domElementId ); - if ( isDefined( domElement ) ) { - _observables[ storageId ].originalObject = domElement.innerHTML; + if ( isDefined( domElement ) ) { + watch.originalObject = domElement.outerHTML; + } } - } - var cachedObject = _observables[ storageId ].cachedObject, - originalObject = _observables[ storageId ].originalObject, - originalObjectJson = !isDomElement ? JSON.stringify( originalObject ) : originalObject; + var cachedObject = watch.cachedObject, + originalObject = watch.originalObject, + originalObjectJson = !isDomElement ? JSON.stringify( originalObject ) : originalObject; - if ( cachedObject !== originalObjectJson ) { - _observables[ storageId ].cachedObject = originalObjectJson; + if ( cachedObject !== originalObjectJson ) { + var watchOptions = watch.options; - var options = _observables[ storageId ].options; + if ( watchOptions.reset ) { + if ( isDomElement ) { + domElement.outerHTML = watch.cachedObject; + } else { + watch.originalObject = getObjectFromString( cachedObject ).result; + } - if ( isDomElement ) { - fireCustomTrigger( options.onChange, cachedObject, originalObjectJson ); - } else { + } else { + watch.cachedObject = originalObjectJson; + } + + if ( isDomElement ) { + fireCustomTrigger( watchOptions.onChange, cachedObject, originalObjectJson ); + } else { + + var oldValue = getObjectFromString( cachedObject ).result, + newValue = getObjectFromString( originalObjectJson ).result; + + fireCustomTrigger( watchOptions.onChange, oldValue, newValue ); - var oldValue = getObjectFromString( cachedObject ).result, - newValue = getObjectFromString( originalObjectJson ).result; + if ( isDefinedFunction( watchOptions.onPropertyChange ) && !isDefinedArray( oldValue ) ) { + compareWatchObjectProperties( oldValue, newValue, watchOptions ); + } + } - fireCustomTrigger( options.onChange, oldValue, newValue ); + if ( watchOptions.cancelOnChange ) { + cancelWatchObject( storageId ); + } - if ( isDefinedFunction( options.onPropertyChange ) && !isDefinedArray( oldValue ) ) { - compareObservableObjectProperties( oldValue, newValue, options ); + watch.totalChanges++; + + if ( watchOptions.maximumChangesBeforeCanceling > 0 && watch.totalChanges >= watchOptions.maximumChangesBeforeCanceling ) { + cancelWatchObject( storageId ); } } } } - function compareObservableObjectProperties( oldObject, newObject, options ) { + function compareWatchObjectProperties( oldObject, newObject, options ) { for ( var propertyName in oldObject ) { if ( oldObject.hasOwnProperty( propertyName ) ) { var propertyOldValue = oldObject[ propertyName ], @@ -185,7 +212,7 @@ } if ( isDefinedObject( propertyOldValue ) && isDefinedObject( propertyNewValue ) ) { - compareObservableObjectProperties( propertyOldValue, propertyNewValue, options ); + compareWatchObjectProperties( propertyOldValue, propertyNewValue, options ); } else { if ( JSON.stringify( propertyOldValue ) !== JSON.stringify( propertyNewValue ) ) { @@ -196,20 +223,43 @@ } } + function cancelWatchObject( storageId ) { + if ( _watches.hasOwnProperty( storageId ) ) { + var watchOptions = _watches[ storageId ].options; + + fireCustomTrigger( watchOptions.onCancel, storageId ); + + clearTimeout( _watches[ storageId ].timer ); + delete _watches[ storageId ]; + } + } + /* * ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - * Observe Options + * Watch Options * ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ - function getObserveOptions( newOptions ) { + function getWatchOptions( newOptions ) { var options = !isDefinedObject( newOptions ) ? {} : newOptions; - options.observeTimeout = getDefaultNumber( options.observeTimeout, 250 ); + options.timeout = getDefaultNumber( options.timeout, 250 ); + options.starts = getDefaultDate( options.starts, null ); options.expires = getDefaultDate( options.expires, null ); + options.reset = getDefaultBoolean( options.reset, false ); + options.cancelOnChange = getDefaultBoolean( options.cancelOnChange, false ); + options.maximumChangesBeforeCanceling = getDefaultNumber( options.maximumChangesBeforeCanceling, 0 ); + + options = getWatchOptionsCustomTriggers( options ); + + return options; + } + + function getWatchOptionsCustomTriggers( options ) { options.onChange = getDefaultFunction( options.onChange, null ); options.onPropertyChange = getDefaultFunction( options.onPropertyChange, null ); + options.onCancel = getDefaultFunction( options.onCancel, null ); return options; } @@ -374,7 +424,7 @@ /* * ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - * Public Functions: Observables + * Public Functions: Watching Objects * ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ @@ -391,7 +441,7 @@ * @returns {string} The ID that object watch is stored under. */ this.watchObject = function( object, options ) { - return createObservableObject( object, options ); + return createWatch( object, options ); }; /** @@ -401,44 +451,55 @@ * * @public * - * @param {string} id The Id of the object being watched. + * @param {string} id The Id of the object being watched, or DOM element ID being watched. * * @returns {boolean} States if the object being watched has been canceled. */ this.cancelWatch = function( id ) { var result = false; - if ( _observables.hasOwnProperty( id ) ) { - clearTimeout( _observables[ id ].timer ); - delete _observables[ id ]; + if ( _watches.hasOwnProperty( id ) ) { + cancelWatchObject( id ); result = true; + } else { + + for ( var storageId in _watches ) { + if ( _watches.hasOwnProperty( storageId ) && isDefinedString( _watches[ storageId ].domElementId ) && _watches[ storageId ].domElementId === id ) { + cancelWatchObject( storageId ); + + result = true; + break; + } + } } return result; }; /** - * cancelDomElementWatch(). + * getWatch(). * - * Cancels the watching of a DOM element object for changes. + * Returns the properties for an active watch. * * @public * - * @param {string} elementId The Id of the DOM element object being watched. + * @param {string} id The Id of the object being watched, or DOM element ID being watched. * - * @returns {boolean} States if the DOM element object being watched has been canceled. + * @returns {Object} The watch properties for an object (null if not found). */ - this.cancelDomElementWatch = function( elementId ) { - var result = false; + this.getWatch = function( id ) { + var result = null; + + if ( _watches.hasOwnProperty( id ) ) { + result = _watches[ id ]; + } else { - for ( var storageId in _observables ) { - if ( _observables.hasOwnProperty( storageId ) && isDefinedString( _observables[ storageId ].domElementId ) && _observables[ storageId ].domElementId === elementId ) { - clearTimeout( _observables[ storageId ].timer ); - delete _observables[ storageId ]; - - result = true; - break; + for ( var storageId in _watches ) { + if ( _watches.hasOwnProperty( storageId ) && isDefinedString( _watches[ storageId ].domElementId ) && _watches[ storageId ].domElementId === id ) { + result = _watches[ storageId ]; + break; + } } } @@ -493,7 +554,7 @@ * @returns {string} The version number. */ this.getVersion = function() { - return "0.1.0"; + return "0.2.0"; }; diff --git a/test/dist/observe.js.basic.html b/test/dist/observe.js.basic.html index 7b2ebd3..f20b0ca 100644 --- a/test/dist/observe.js.basic.html +++ b/test/dist/observe.js.basic.html @@ -11,18 +11,24 @@

Observe.js - Basic

-

This is a basic example of how to use Observe.js an HTML DOM element.

+

This is a basic example of how to use Observe.js to watch HTML DOM elements, and JS objects.

-
- When changed, this element should fire a custom trigger. +
+
+ When changed, this element should fire a custom trigger. +
+ +
+ When changed, this element should fire a custom trigger (and reset its value back to the original). +

Watch Object:

- +

Change Object Values:

@@ -30,7 +36,13 @@

Change Object Values:


- + + +
+ +

Get Watch Object:

+ +

Configuration:

@@ -56,7 +68,8 @@

Additional Data:

function watchNewObject() { watchObjectId = $observe.watchObject( window.observeObject, { onChange: onObservableObjectChange, - onPropertyChange: onObservableObjectPropertyChange + onPropertyChange: onObservableObjectPropertyChange, + onCancel: onCancelWatch } ); } @@ -78,5 +91,10 @@

Additional Data:

console.log( "New Value: " + JSON.stringify( newValue ) ); console.log( "" ); } + + function onCancelWatch( id ) { + console.log( "Watch " + id + " canceled." ); + console.log( "" ); + } \ No newline at end of file diff --git a/test/dist/observe.js.min.html b/test/dist/observe.js.min.html index 1bbb1c7..94fd2d6 100644 --- a/test/dist/observe.js.min.html +++ b/test/dist/observe.js.min.html @@ -11,18 +11,24 @@

Observe.js - Basic

-

This is a basic example of how to use Observe.js an HTML DOM element.

+

This is a basic example of how to use Observe.js to watch HTML DOM elements, and JS objects.

-
- When changed, this element should fire a custom trigger. +
+
+ When changed, this element should fire a custom trigger. +
+ +
+ When changed, this element should fire a custom trigger (and reset its value back to the original). +

Watch Object:

- +

Change Object Values:

@@ -30,7 +36,13 @@

Change Object Values:


- + + +
+ +

Get Watch Object:

+ +

Configuration:

@@ -56,7 +68,8 @@

Additional Data:

function watchNewObject() { watchObjectId = $observe.watchObject( window.observeObject, { onChange: onObservableObjectChange, - onPropertyChange: onObservableObjectPropertyChange + onPropertyChange: onObservableObjectPropertyChange, + onCancel: onCancelWatch } ); } @@ -78,5 +91,10 @@

Additional Data:

console.log( "New Value: " + JSON.stringify( newValue ) ); console.log( "" ); } + + function onCancelWatch( id ) { + console.log( "Watch " + id + " canceled." ); + console.log( "" ); + } \ No newline at end of file diff --git a/test/src/observe.js.basic.html b/test/src/observe.js.basic.html index ba22d91..6010161 100644 --- a/test/src/observe.js.basic.html +++ b/test/src/observe.js.basic.html @@ -11,18 +11,24 @@

Observe.js - Basic

-

This is a basic example of how to use Observe.js an HTML DOM element.

+

This is a basic example of how to use Observe.js to watch HTML DOM elements, and JS objects.

-
- When changed, this element should fire a custom trigger. +
+
+ When changed, this element should fire a custom trigger. +
+ +
+ When changed, this element should fire a custom trigger (and reset its value back to the original). +

Watch Object:

- +

Change Object Values:

@@ -30,7 +36,13 @@

Change Object Values:


- + + +
+ +

Get Watch Object:

+ +

Configuration:

@@ -56,7 +68,8 @@

Additional Data:

function watchNewObject() { watchObjectId = $observe.watchObject( window.observeObject, { onChange: onObservableObjectChange, - onPropertyChange: onObservableObjectPropertyChange + onPropertyChange: onObservableObjectPropertyChange, + onCancel: onCancelWatch } ); } @@ -78,5 +91,10 @@

Additional Data:

console.log( "New Value: " + JSON.stringify( newValue ) ); console.log( "" ); } + + function onCancelWatch( id ) { + console.log( "Watch " + id + " canceled." ); + console.log( "" ); + } \ No newline at end of file