diff --git a/README.md b/README.md index b2aff3c..53e5e09 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ Idiomorph supports the following options: | `morphStyle` | The style of morphing to use, either `innerHTML` or `outerHTML` | `Idiomorph.morph(..., {morphStyle:'innerHTML'})` | | `ignoreActive` | If set to `true`, idiomorph will skip the active element | `Idiomorph.morph(..., {ignoreActive:true})` | | `ignoreActiveValue` | If set to `true`, idiomorph will not update the active element's value | `Idiomorph.morph(..., {ignoreActiveValue:true})` | +| `ignoreValue` | If set to `true`, idiomorph will not update any element's value including inputs, checkboxes and textarea | `Idiomorph.morph(..., {ignoreValue:true})` | | `restoreFocus` | If set to `true`, idiomorph will attempt to restore any lost focus and selection state after the morph. | `Idiomorph.morph(..., {restoreFocus:true})` | | `head` | Allows you to control how the `head` tag is merged. See the [head](#the-head-tag) section for more details | `Idiomorph.morph(..., {head:{style:merge}})` | | `callbacks` | Allows you to insert callbacks when events occur in the morph life cycle, see the callback table below | `Idiomorph.morph(..., {callbacks:{beforeNodeAdded:function(node){...}})` | diff --git a/src/idiomorph.js b/src/idiomorph.js index bfa9784..4af2f13 100644 --- a/src/idiomorph.js +++ b/src/idiomorph.js @@ -28,6 +28,7 @@ * @property {'outerHTML' | 'innerHTML'} [morphStyle] * @property {boolean} [ignoreActive] * @property {boolean} [ignoreActiveValue] + * @property {boolean} [ignoreValue] * @property {boolean} [restoreFocus] * @property {ConfigCallbacks} [callbacks] * @property {ConfigHead} [head] @@ -69,6 +70,7 @@ * @property {'outerHTML' | 'innerHTML'} morphStyle * @property {boolean} [ignoreActive] * @property {boolean} [ignoreActiveValue] + * @property {boolean} [ignoreValue] * @property {boolean} [restoreFocus] * @property {ConfigCallbacksInternal} callbacks * @property {ConfigHeadInternal} head @@ -106,6 +108,7 @@ var Idiomorph = (function () { * @property {ConfigInternal['morphStyle']} morphStyle * @property {ConfigInternal['ignoreActive']} ignoreActive * @property {ConfigInternal['ignoreActiveValue']} ignoreActiveValue + * @property {ConfigInternal['ignoreValue']} ignoreValue * @property {ConfigInternal['restoreFocus']} restoreFocus * @property {Map>} idMap * @property {Set} persistentIds @@ -601,7 +604,8 @@ var Idiomorph = (function () { ); } else { morphAttributes(oldNode, newContent, ctx); - if (!ignoreValueOfActiveElement(oldNode, ctx)) { + if (!ignoreValueOfActiveElement(oldNode, ctx) && + (!ctx.ignoreValue || !(oldNode instanceof HTMLTextAreaElement))) { // @ts-ignore newContent can be a node here because .firstChild will be null morphChildren(ctx, oldNode, newContent); } @@ -653,7 +657,7 @@ var Idiomorph = (function () { } } - if (!ignoreValueOfActiveElement(oldElt, ctx)) { + if (!ctx.ignoreValue && !ignoreValueOfActiveElement(oldElt, ctx)) { syncInputValue(oldElt, newElt, ctx); } } @@ -774,9 +778,8 @@ var Idiomorph = (function () { */ function ignoreAttribute(attr, element, updateType, ctx) { if ( - attr === "value" && - ctx.ignoreActiveValue && - element === document.activeElement + attr === "value" && (ctx.ignoreValue || + (ctx.ignoreActiveValue && element === document.activeElement)) ) { return true; } @@ -962,6 +965,7 @@ var Idiomorph = (function () { morphStyle: morphStyle, ignoreActive: mergedConfig.ignoreActive, ignoreActiveValue: mergedConfig.ignoreActiveValue, + ignoreValue: mergedConfig.ignoreValue, restoreFocus: mergedConfig.restoreFocus, idMap: idMap, persistentIds: persistentIds, diff --git a/test/core.js b/test/core.js index 3b15501..618ab97 100644 --- a/test/core.js +++ b/test/core.js @@ -366,6 +366,51 @@ describe("Core morphing tests", function () { document.body.removeChild(parent); }); + it("ignore input value change when ignoreValue is true", function () { + let parent = make("
"); + document.body.append(parent); + + let initial = parent.querySelector("input"); + + // morph + let finalSrc = ''; + Idiomorph.morph(initial, finalSrc, { morphStyle: "outerHTML" }); + initial.outerHTML.should.equal(''); + + document.activeElement.should.equal(document.body); + + let finalSrc2 = ''; + Idiomorph.morph(initial, finalSrc2, { + morphStyle: "outerHTML", + ignoreValue: true, + }); + initial.value.should.equal("bar"); + initial.classList.value.should.equal("foo"); + + document.body.removeChild(parent); + }); + + it("ignores textarea value when ignoreValue is true", function () { + let parent = make("
"); + document.body.append(parent); + let initial = parent.querySelector("textarea"); + + let finalSrc = ""; + Idiomorph.morph(initial, finalSrc, { morphStyle: "outerHTML" }); + initial.outerHTML.should.equal(""); + + document.activeElement.should.equal(document.body); + + let finalSrc2 = ''; + Idiomorph.morph(initial, finalSrc2, { + morphStyle: "outerHTML", + ignoreValue: true, + }); + initial.outerHTML.should.equal(''); + + document.body.removeChild(parent); + }); + it("can morph input value properly because value property is special and doesnt reflect", function () { let initial = make('
'); let final = make(''); @@ -419,6 +464,70 @@ describe("Core morphing tests", function () { document.body.removeChild(parent); }); + it("morph does not remove input checked with ignoreValue set", function () { + let parent = make('
'); + document.body.append(parent); + let initial = parent.querySelector("input"); + initial.checked = true; + + let finalSrc = ''; + Idiomorph.morph(initial, finalSrc, { morphStyle: "outerHTML", ignoreValue: true }); + initial.outerHTML.should.equal(''); + initial.checked.should.equal(true); + document.body.removeChild(parent); + }); + + it("morph does not reset input checked with ignoreValue set", function () { + let parent = make('
'); + document.body.append(parent); + let initial = parent.querySelector("input"); + initial.checked = false; + + let finalSrc = ''; + Idiomorph.morph(initial, finalSrc, { morphStyle: "outerHTML", ignoreValue: true }); + initial.outerHTML.should.equal(''); + initial.checked.should.equal(false); + document.body.removeChild(parent); + }); + + it("morph does still set input checked when checked attribute added even with ignoreValue set", function () { + let parent = make('
'); + document.body.append(parent); + let initial = parent.querySelector("input"); + + let MiddleSrc = ''; + Idiomorph.morph(initial, MiddleSrc, { morphStyle: "outerHTML"}); + + initial.outerHTML.should.equal(''); + initial.checked.should.equal(false); + + let finalSrc = ''; + + Idiomorph.morph(initial, finalSrc, { morphStyle: "outerHTML", ignoreValue: true}); + initial.outerHTML.should.equal(''); + initial.checked.should.equal(true); + document.body.removeChild(parent); + }); + + it("morph does still remove input checked when checked attribute removed even with ignoreValue set", function () { + let parent = make('
'); + document.body.append(parent); + let initial = parent.querySelector("input"); + + let MiddleSrc = ''; + Idiomorph.morph(initial, MiddleSrc, { morphStyle: "outerHTML"}); + + initial.outerHTML.should.equal(''); + initial.checked.should.equal(true); + + let finalSrc = ''; + + Idiomorph.morph(initial, finalSrc, { morphStyle: "outerHTML", ignoreValue: true}); + initial.outerHTML.should.equal(''); + initial.checked.should.equal(false); + document.body.removeChild(parent); + }); + it("can morph input checked properly, set checked property to true", function () { let parent = make('
'); document.body.append(parent);