diff --git a/package.json b/package.json index e1ec452c..6303ce1d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "node-test": "babel-node test/node-test/jasmine.js", "node-cover": "babel-node node_modules/.bin/babel-istanbul cover test/node-test/jasmine.js && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", "check-coverage": "babel-istanbul check-coverage --lines 95", - "develop": "karma start test/karma.conf.js", + "develop": "CHROME_BIN=chromium-browser karma start test/karma-test/karma.conf.js", "karma-test": "CHROME_BIN=chromium-browser karma start test/karma-test/karma.conf.js --single-run --no-auto-watch", "karma-libraries-test": "npm run karma-test && npm run karma-test -- --dom-library=jquery-1 && npm run karma-test -- --dom-library=jquery-2 && npm run karma-test -- --dom-library=jquery-3 && npm run karma-test -- --dom-library=zepto", "watch": "webpack --config ./webpack.config.js --watch", diff --git a/src/array/_processrendering/getalreadyrendered.js b/src/array/_processrendering/getalreadyrendered.js index ba5a1b04..f84a8a22 100644 --- a/src/array/_processrendering/getalreadyrendered.js +++ b/src/array/_processrendering/getalreadyrendered.js @@ -15,7 +15,8 @@ export default function getAlreadyRendered({ // if item's node is already rendered for an array then return it if (renderedInArrays && renderedInArrays[selfId]) { - return renderedInArrays[selfId]; + const node = renderedInArrays[selfId]; + return node.__matreshkaReplacedByNode || node; } } diff --git a/src/array/_processrendering/renderitemnode.js b/src/array/_processrendering/renderitemnode.js index bc472126..ed21ccc0 100644 --- a/src/array/_processrendering/renderitemnode.js +++ b/src/array/_processrendering/renderitemnode.js @@ -106,7 +106,8 @@ export default function renderItemNode({ throw matreshkaError('array:rendered_number_nodes', { length: parsed.length }); } - const node = renderedInArrays[selfId] = parsed[0]; + let node = renderedInArrays[selfId] = parsed[0]; + node = node.__matreshkaReplacedByNode || node; if (bindRenderedAsSandbox) { if (forceRerender) { diff --git a/src/binders/existence.js b/src/binders/existence.js new file mode 100644 index 00000000..80e1f798 --- /dev/null +++ b/src/binders/existence.js @@ -0,0 +1,43 @@ +export default function existence(switcher = true) { + let comment; + + return { + setValue(value) { + const node = this; + const { tagName, id, classList, className } = node; + + if (!comment) { + let commentText = tagName; + + + if (id) { + commentText += `#${id}`; + } + + if (className) { + commentText += `.${[].slice.apply(classList).join('.')}`; + } + + comment = window.document.createComment(commentText); + } + + value = !switcher ? !value : value; // eslint-disable-line no-param-reassign + + if (value) { + delete node.__matreshkaReplacedByNode; + if (comment.parentNode) { + comment.parentNode.insertBefore(node, comment); + comment.parentNode.removeChild(comment); + } + } + + if (!value) { + node.__matreshkaReplacedByNode = comment; + if (node.parentNode) { + node.parentNode.insertBefore(comment, node); + node.parentNode.removeChild(node); + } + } + } + }; +} diff --git a/src/binders/index.js b/src/binders/index.js index 29dc6187..875194be 100644 --- a/src/binders/index.js +++ b/src/binders/index.js @@ -11,6 +11,7 @@ import progress from './progress'; import text from './text'; import style from './style'; import dataset from './dataset'; +import existence from './existence'; export { html, @@ -25,5 +26,6 @@ export { progress, text, style, - dataset + dataset, + existence }; diff --git a/src/select.js b/src/select.js index e8336339..f8e14bf3 100644 --- a/src/select.js +++ b/src/select.js @@ -38,7 +38,7 @@ export default function select(object, selector) { if (bindings) { // iterate over all bound nodes trying to find a descendant matched given selector for (let i = 0; i < bindings.length; i++) { - const { node } = bindings[i]; + const node = bindings[i].node; const selected = node.querySelector(selector); if (selected) { diff --git a/test/spec/bindings/existence_binder_spec.js b/test/spec/bindings/existence_binder_spec.js new file mode 100644 index 00000000..3a64b631 --- /dev/null +++ b/test/spec/bindings/existence_binder_spec.js @@ -0,0 +1,140 @@ +/* eslint-disable import/no-extraneous-dependencies, import/extensions */ +import bindNode from 'src/bindnode'; +import select from 'src/select'; +import MatreshkaArray from 'src/array'; +import { existence } from 'src/binders'; + +describe('Existence binder', () => { + const noDebounceFlag = { + debounceSetValue: false, + debounceGetValue: false + }; + + let obj; + let node; + let parent; + + beforeEach(() => { + obj = {}; + node = window.document.createElement('div'); + node.innerHTML = '
'; + parent = window.document.createElement('div'); + parent.appendChild(node); + }); + + it('should allow to use exitence binder', () => { + node.id = 'foo'; + node.className = 'bar baz'; + + obj.x = false; + bindNode(obj, 'x', node, existence(), noDebounceFlag); + + expect(parent.childNodes.length).toEqual(1); + expect(parent.childNodes[0].nodeName).toEqual('#comment'); + expect(parent.childNodes[0].nodeValue).toEqual('DIV#foo.bar.baz'); + + obj.x = true; + + expect(parent.childNodes.length).toEqual(1); + expect(parent.childNodes[0].tagName).toEqual('DIV'); + + obj.x = false; // try again + + expect(parent.childNodes.length).toEqual(1); + expect(parent.childNodes[0].nodeName).toEqual('#comment'); + expect(parent.childNodes[0].nodeValue).toEqual('DIV#foo.bar.baz'); + + obj.x = true; // try again + + expect(parent.childNodes.length).toEqual(1); + expect(parent.childNodes[0].tagName).toEqual('DIV'); + }); + + it('should allow to use exitence binder with reverse behavior', () => { + node.id = 'foo'; + node.className = 'bar baz'; + + obj.x = false; + bindNode(obj, 'x', node, existence(false), noDebounceFlag); + + expect(parent.childNodes.length).toEqual(1); + expect(parent.childNodes[0].nodeName).toEqual('DIV'); + + obj.x = true; + + expect(parent.childNodes.length).toEqual(1); + expect(parent.childNodes[0].nodeName).toEqual('#comment'); + expect(parent.childNodes[0].nodeValue).toEqual('DIV#foo.bar.baz'); + + obj.x = false; // try again + + expect(parent.childNodes.length).toEqual(1); + expect(parent.childNodes[0].nodeName).toEqual('DIV'); + + obj.x = true; // try again + + expect(parent.childNodes.length).toEqual(1); + expect(parent.childNodes[0].nodeName).toEqual('#comment'); + expect(parent.childNodes[0].nodeValue).toEqual('DIV#foo.bar.baz'); + }); + + it('should allow to select nodes inside original element', () => { + obj.x = false; + bindNode(obj, 'x', node, existence(), noDebounceFlag); + + expect(select(obj, ':bound(x)')).toEqual(node); + expect(select(obj, ':bound(x) .foo').id).toEqual('foo'); + + obj.x = true; + expect(select(obj, ':bound(x)')).toEqual(node); + expect(select(obj, ':bound(x) .foo').id).toEqual('foo'); + + obj.x = false; + expect(select(obj, ':bound(x)')).toEqual(node); + expect(select(obj, ':bound(x) .foo').id).toEqual('foo'); + }); + + it('should be possible to bind array item and manipulate with an array', () => { + const arr = new MatreshkaArray(); + arr.itemRenderer = '
'; + + bindNode(arr, 'sandbox', '
'); + + arr.push( + { x: 3, exists: true }, + { x: 1, exists: false }, + { x: 5, exists: true }, + { x: 2, exists: false }, + { x: 4, exists: true } + ); + + for (const item of arr) { + bindNode(item, 'exists', ':sandbox', existence(), noDebounceFlag); + console.log(select(item, ':sandbox').__matreshkaReplacedByNode); + } + + expect( + Array.from(arr.nodes.sandbox.childNodes).map(({ nodeName }) => nodeName) + ).toEqual(['DIV', '#comment', 'DIV', '#comment', 'DIV']); + + arr.sort((a, b) => (a.x > b.x ? 1 : -1)); + + expect( + Array.from(arr.nodes.sandbox.childNodes).map(({ nodeName }) => nodeName) + ).toEqual(['#comment', '#comment', 'DIV', 'DIV', 'DIV']); + + arr.reverse(); + + expect( + Array.from(arr.nodes.sandbox.childNodes).map(({ nodeName }) => nodeName) + ).toEqual(['DIV', 'DIV', 'DIV', '#comment', '#comment']); + + arr[0].exists = false; + + arr[4].exists = true; + + expect( + Array.from(arr.nodes.sandbox.childNodes).map(({ nodeName }) => nodeName) + ).toEqual(['#comment', 'DIV', 'DIV', '#comment', 'DIV']); + }); +});