From 9e6b175519dbcc78303d7315ffb08c6a53488156 Mon Sep 17 00:00:00 2001 From: Ilya Sarantsev Date: Mon, 21 Aug 2017 18:55:42 +0300 Subject: [PATCH 1/7] Handle shared attributes object in hyperscript (#1941) --- docs/change-log.md | 1 + render/hyperscript.js | 12 ++++++++++++ render/tests/test-hyperscript.js | 17 +++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/docs/change-log.md b/docs/change-log.md index 82686f0e..e73ff1b6 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -36,6 +36,7 @@ #### Bug fixes: - core: don't call `onremove` on the children of components that return null from the view [#1921](https://github.com/MithrilJS/mithril.js/issues/1921) [octavore](https://github.com/octavore) ([#1922](https://github.com/MithrilJS/mithril.js/pull/1922)) +- hypertext: correct handling of shared attributes object passed to `m()`. Will copy attributes when it's necessary [#1941](https://github.com/MithrilJS/mithril.js/issues/1941) [s-ilya](https://github.com/s-ilya) ([#1942](https://github.com/MithrilJS/mithril.js/pull/1942)) --- diff --git a/render/hyperscript.js b/render/hyperscript.js index 5b5fda29..ea56c893 100644 --- a/render/hyperscript.js +++ b/render/hyperscript.js @@ -28,6 +28,18 @@ function execSelector(state, attrs, children) { var hasAttrs = false, childList, text var className = attrs.className || attrs.class + if (Object.keys(state.attrs).length && Object.keys(attrs).length) { + var newAttrs = {} + + for(var key in attrs) { + if (hasOwn.call(attrs, key)) { + newAttrs[key] = attrs[key] + } + } + + attrs = newAttrs + } + for (var key in state.attrs) { if (hasOwn.call(state.attrs, key)) { attrs[key] = state.attrs[key] diff --git a/render/tests/test-hyperscript.js b/render/tests/test-hyperscript.js index 498a9dbf..f7c5f889 100644 --- a/render/tests/test-hyperscript.js +++ b/render/tests/test-hyperscript.js @@ -507,6 +507,23 @@ o.spec("hyperscript", function() { o(vnode.children[0].tag).equals("i") o(vnode.children[1].tag).equals("s") }) + o("handles shared attrs", function() { + var attrs = {a: "b"} + + var nodeA = m(".a", attrs) + var nodeB = m(".b", attrs) + + o(nodeA.attrs.className).equals("a") + o(nodeA.attrs.a).equals("b") + + o(nodeB.attrs.className).equals("b") + o(nodeB.attrs.a).equals("b") + }) + o("doesnt modify passed attributes object", function() { + var attrs = {a: "b"} + m(".a", attrs) + o(attrs).deepEquals({a: "b"}) + }) o("handles fragment children without attr unwrapped", function() { var vnode = m("div", [m("i")], [m("s")]) From 106fd7514572d7ea600dd6493290414bbec6e4a5 Mon Sep 17 00:00:00 2001 From: Ilya Sarantsev Date: Sat, 26 Aug 2017 22:42:29 +0300 Subject: [PATCH 2/7] Performance optimization for #1941 per @isiahmeadows suggestion --- render/hyperscript.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/render/hyperscript.js b/render/hyperscript.js index ea56c893..00c438c9 100644 --- a/render/hyperscript.js +++ b/render/hyperscript.js @@ -6,6 +6,11 @@ var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\[" var selectorCache = {} var hasOwn = {}.hasOwnProperty +function isEmpty(object) { + for (var key in object) if (hasOwn.call(object, key)) return false + return true +} + function compileSelector(selector) { var match, tag = "div", classes = [], attrs = {} while (match = selectorParser.exec(selector)) { @@ -28,7 +33,7 @@ function execSelector(state, attrs, children) { var hasAttrs = false, childList, text var className = attrs.className || attrs.class - if (Object.keys(state.attrs).length && Object.keys(attrs).length) { + if (!isEmpty(state.attrs) && !isEmpty(attrs)) { var newAttrs = {} for(var key in attrs) { From 19969e25558ac7d68d86dcad614d918f9fc132b1 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Sun, 27 Aug 2017 13:57:53 +0000 Subject: [PATCH 3/7] Bundled output for commit c9629ffcbca3ac535c675ee1b4d29c0b6bb07bb4 [skip ci] --- README.md | 2 +- mithril.js | 13 ++++++++ mithril.min.js | 89 +++++++++++++++++++++++++------------------------- 3 files changed, 59 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 6ddbbb0a..d96eb04b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ mithril.js [![NPM Version](https://img.shields.io/npm/v/mithril.svg)](https://ww ## What is Mithril? -A modern client-side Javascript framework for building Single Page Applications. It's small (8.29 KB gzipped), fast and provides routing and XHR utilities out of the box. +A modern client-side Javascript framework for building Single Page Applications. It's small (8.33 KB gzipped), fast and provides routing and XHR utilities out of the box. Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍. diff --git a/mithril.js b/mithril.js index e2c8944e..03d2224e 100644 --- a/mithril.js +++ b/mithril.js @@ -17,6 +17,10 @@ Vnode.normalizeChildren = function normalizeChildren(children) { var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g var selectorCache = {} var hasOwn = {}.hasOwnProperty +function isEmpty(object) { + for (var key in object) if (hasOwn.call(object, key)) return false + return true +} function compileSelector(selector) { var match, tag = "div", classes = [], attrs = {} while (match = selectorParser.exec(selector)) { @@ -37,6 +41,15 @@ function compileSelector(selector) { function execSelector(state, attrs, children) { var hasAttrs = false, childList, text var className = attrs.className || attrs.class + if (!isEmpty(state.attrs) && !isEmpty(attrs)) { + var newAttrs = {} + for(var key in attrs) { + if (hasOwn.call(attrs, key)) { + newAttrs[key] = attrs[key] + } + } + attrs = newAttrs + } for (var key in state.attrs) { if (hasOwn.call(state.attrs, key)) { attrs[key] = state.attrs[key] diff --git a/mithril.min.js b/mithril.min.js index 05686d0c..9b3d144a 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,44 +1,45 @@ -(function(){function y(b,d,f,m,q,h){return{tag:b,key:d,attrs:f,children:m,text:q,dom:h,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function A(b){var d,f=arguments[1],m=2;if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");if("string"===typeof b&&!(d=M[b])){var q="div";for(var h=[],k={};d=Q.exec(b);){var r=d[1],g=d[2];""===r&&""!==g?q=g:"#"===r?k.id=g:"."===r?h.push(g): -"["===d[3][0]&&((r=d[6])&&(r=r.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===d[4]?h.push(r):k[d[4]]=""===r?r:r||!0)}0a.indexOf("?")?"?":"&";a+=f+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(z){throw Error(a);}}function r(a){return a.responseText} -function g(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;dn.status||304===n.status||S.test(a.url))d(g(a.type,b));else{var l=Error(n.responseText);l.code=n.status;l.response=b;f(l)}}catch(c){f(c)}};m&&null!=a.data?n.send(a.data):n.send()});return!0===a.background?z:p(z)},jsonp:function(a,k){var r=f();a=m(a,k);var p=new d(function(d,f){var k=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+n++,m=b.document.createElement("script");b[k]=function(f){m.parentNode.removeChild(m);d(g(a.type,f));delete b[k]};m.onerror=function(){m.parentNode.removeChild(m); -f(Error("JSONP request failed"));delete b[k]};null==a.data&&(a.data={});a.url=q(a.url,a.data);a.data[a.callbackKey||"callback"]=k;m.src=h(a.url,a.data);b.document.documentElement.appendChild(m)});return!0===a.background?p:r(p)},setCompletionCallback:function(a){p=a}}}(window,w),P=function(b){function d(l,c,e,a,b,d,g){for(;e=p&&E>=B;){var u=c[p];t=e[B];if(u!==t||b)if(null==u)p++;else if(null==t)B++;else if(u.key===t.key){var x=null!=z&&p>=c.length-z.length||null==z&&b;p++;B++;k(l,u,t,m,g(c,p,h),x,q);b&&u.tag===t.tag&&n(l,r(u),h)}else if(u=c[v],u!==t||b)if(null==u)v--;else if(null==t)B++;else if(u.key===t.key)x=null!=z&&v>=c.length- -z.length||null==z&&b,k(l,u,t,m,g(c,v+1,h),x,q),(b||B=p&&E>=B;){u=c[v];t=e[E];if(u!==t||b)if(null==u)v--;else{if(null!=t)if(u.key===t.key)x=null!=z&&v>=c.length-z.length||null==z&&b,k(l,u,t,m,g(c,v+1,h),x,q),b&&u.tag===t.tag&&n(l,r(u),h),null!=u.dom&&(h=u.dom),v--;else{if(!F){F=c;x=v;u={};var w;for(w=0;wa.indexOf("?")?"?":"&";a+=e+d}return a}function g(a){try{return""!==a?JSON.parse(a):null}catch(z){throw Error(a);}}function r(a){return a.responseText}function l(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;dm.status||304===m.status||T.test(a.url))d(l(a.type,b));else{var h=Error(m.responseText);h.code=m.status;h.response=b;e(h)}}catch(c){e(c)}};k&&null!=a.data?m.send(a.data):m.send()});return!0===a.background?z:p(z)},jsonp:function(a,g){var r=e();a=k(a,g);var p=new d(function(d,e){var g=a.callbackName||"_mithril_"+ +Math.round(1E16*Math.random())+"_"+m++,k=b.document.createElement("script");b[g]=function(e){k.parentNode.removeChild(k);d(l(a.type,e));delete b[g]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete b[g]};null==a.data&&(a.data={});a.url=n(a.url,a.data);a.data[a.callbackKey||"callback"]=g;k.src=f(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?p:r(p)},setCompletionCallback:function(a){p=a}}}(window,w),Q=function(b){function d(h, +c,q,a,b,d,l){for(;q=p&&E>=B;){var t=c[p];u=q[B];if(t!==u||b)if(null==t)p++;else if(null==u)B++;else if(t.key===u.key){var x= +null!=z&&p>=c.length-z.length||null==z&&b;p++;B++;g(h,t,u,k,l(c,p,f),x,n);b&&t.tag===u.tag&&m(h,r(t),f)}else if(t=c[v],t!==u||b)if(null==t)v--;else if(null==u)B++;else if(t.key===u.key)x=null!=z&&v>=c.length-z.length||null==z&&b,g(h,t,u,k,l(c,v+1,f),x,n),(b||B=p&&E>=B;){t=c[v];u=q[E];if(t!==u||b)if(null==t)v--;else{if(null!=u)if(t.key===u.key)x=null!=z&&v>=c.length-z.length||null==z&&b,g(h,t,u,k,l(c,v+1,f),x,n),b&&t.tag=== +u.tag&&m(h,r(t),f),null!=t.dom&&(f=t.dom),v--;else{if(!F){F=c;x=v;t={};var w;for(w=0;w Date: Tue, 22 Aug 2017 14:04:24 -0500 Subject: [PATCH 4/7] Fixing IE bug causing active element to be null causing render function to throw error --- docs/change-log.md | 1 + render/render.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/change-log.md b/docs/change-log.md index e73ff1b6..05c1e601 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -28,6 +28,7 @@ - API: `m.route.set()` causes all mount points to be redrawn ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592)) - API: If a user sets the Content-Type header within a request's options, that value will be the entire header value rather than being appended to the default value ([#1924](https://github.com/MithrilJS/mithril.js/pull/1924)) - API: Using style objects in hyperscript calls will now properly diff style properties from one render to another as opposed to re-writing all element style properties every render. +- Fix IE11 active element is null causing render function to throw error. --- diff --git a/render/render.js b/render/render.js index f6782297..0a5938d6 100644 --- a/render/render.js +++ b/render/render.js @@ -630,7 +630,7 @@ module.exports = function($window) { updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) dom.vnodes = vnodes for (var i = 0; i < hooks.length; i++) hooks[i]() - if ($doc.activeElement !== active) active.focus() + if (active !== null && $doc.activeElement !== active) active.focus() } return {render: render, setEventCallback: setEventCallback} From aaa448ac4e84afcfc791a733f48f68d8e288571e Mon Sep 17 00:00:00 2001 From: Jackson Nowotny Date: Tue, 22 Aug 2017 15:51:47 -0500 Subject: [PATCH 5/7] adding comment specifying bug --- render/render.js | 1 + 1 file changed, 1 insertion(+) diff --git a/render/render.js b/render/render.js index 0a5938d6..c733f9fc 100644 --- a/render/render.js +++ b/render/render.js @@ -630,6 +630,7 @@ module.exports = function($window) { updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) dom.vnodes = vnodes for (var i = 0; i < hooks.length; i++) hooks[i]() + // document.activeElement can return null in IE https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement if (active !== null && $doc.activeElement !== active) active.focus() } From 5303e70fde7aa115af4727b52dc8cb728373d001 Mon Sep 17 00:00:00 2001 From: Jackson Nowotny Date: Tue, 29 Aug 2017 10:30:45 -0500 Subject: [PATCH 6/7] fixing merge conflicts --- docs/change-log.md | 2 +- render/render.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/change-log.md b/docs/change-log.md index 05c1e601..5b30bb0a 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -28,7 +28,6 @@ - API: `m.route.set()` causes all mount points to be redrawn ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592)) - API: If a user sets the Content-Type header within a request's options, that value will be the entire header value rather than being appended to the default value ([#1924](https://github.com/MithrilJS/mithril.js/pull/1924)) - API: Using style objects in hyperscript calls will now properly diff style properties from one render to another as opposed to re-writing all element style properties every render. -- Fix IE11 active element is null causing render function to throw error. --- @@ -38,6 +37,7 @@ - core: don't call `onremove` on the children of components that return null from the view [#1921](https://github.com/MithrilJS/mithril.js/issues/1921) [octavore](https://github.com/octavore) ([#1922](https://github.com/MithrilJS/mithril.js/pull/1922)) - hypertext: correct handling of shared attributes object passed to `m()`. Will copy attributes when it's necessary [#1941](https://github.com/MithrilJS/mithril.js/issues/1941) [s-ilya](https://github.com/s-ilya) ([#1942](https://github.com/MithrilJS/mithril.js/pull/1942)) +- Fix IE bug where active element is null causing render function to throw error. ([1943](https://github.com/MithrilJS/mithril.js/pull/1943)) --- diff --git a/render/render.js b/render/render.js index c733f9fc..cc6afd51 100644 --- a/render/render.js +++ b/render/render.js @@ -631,7 +631,7 @@ module.exports = function($window) { dom.vnodes = vnodes for (var i = 0; i < hooks.length; i++) hooks[i]() // document.activeElement can return null in IE https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement - if (active !== null && $doc.activeElement !== active) active.focus() + if (active != null && $doc.activeElement !== active) active.focus() } return {render: render, setEventCallback: setEventCallback} From 67e1aad944efa4ba82f1a9d9b404b0bb7ec0fc19 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Wed, 30 Aug 2017 11:59:27 +0000 Subject: [PATCH 7/7] Bundled output for commit 11183f339d9430da22ab14b9f3bfb5f37df722f8 [skip ci] --- README.md | 2 +- mithril.js | 3 ++- mithril.min.js | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d96eb04b..30e9ab21 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ mithril.js [![NPM Version](https://img.shields.io/npm/v/mithril.svg)](https://ww ## What is Mithril? -A modern client-side Javascript framework for building Single Page Applications. It's small (8.33 KB gzipped), fast and provides routing and XHR utilities out of the box. +A modern client-side Javascript framework for building Single Page Applications. It's small (8.32 KB gzipped), fast and provides routing and XHR utilities out of the box. Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍. diff --git a/mithril.js b/mithril.js index 03d2224e..42d5cbb4 100644 --- a/mithril.js +++ b/mithril.js @@ -991,7 +991,8 @@ var coreRenderer = function($window) { updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) dom.vnodes = vnodes for (var i = 0; i < hooks.length; i++) hooks[i]() - if ($doc.activeElement !== active) active.focus() + // document.activeElement can return null in IE https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement + if (active != null && $doc.activeElement !== active) active.focus() } return {render: render, setEventCallback: setEventCallback} } diff --git a/mithril.min.js b/mithril.min.js index 9b3d144a..0f005532 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -33,13 +33,13 @@ c,d);else if("style"===c)if(a=b,null!=a&&null!=d&&"object"===typeof a&&"object"= c){f=""+d;if(("input"===a.tag||"textarea"===a.tag)&&a.dom.value===f&&a.dom===x.activeElement)return;if("select"===a.tag)if(null===d){if(-1===a.dom.selectedIndex&&a.dom===x.activeElement)return}else if(null!==b&&a.dom.value===f&&a.dom===x.activeElement)return;if("option"===a.tag&&null!=b&&a.dom.value===f)return}"input"===a.tag&&"type"===c?h.setAttribute(c,d):h[c]=d}else"boolean"===typeof d?d?h.setAttribute(c,""):h.removeAttribute(c):h.setAttribute("className"===c?"class":c,d)}}function w(a){return"oninit"=== a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"===a||"onbeforeupdate"===a}function G(a,b,d){var c=a.dom,e="function"!==typeof C?d:function(a){var b=d.call(c,a);C.call(c,a);return b};if(b in c)c[b]="function"===typeof d?e:null;else{var h=b.slice(2);void 0===a.events&&(a.events={});a.events[b]!==e&&(null!=a.events[b]&&c.removeEventListener(h,a.events[b],!1),"function"===typeof d&&(a.events[b]=e,c.addEventListener(h,a.events[b],!1)))}}function A(a,b,d){"function"===typeof a.oninit&& a.oninit.call(b.state,b);"function"===typeof a.oncreate&&d.push(a.oncreate.bind(b.state,b))}function J(a,b,d){"function"===typeof a.onupdate&&d.push(a.onupdate.bind(b.state,b))}var x=b.document,K=x.createDocumentFragment(),P={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"},C;return{render:function(a,b){if(!a)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var c=[],d=x.activeElement,e=a.namespaceURI;null==a.vnodes&&(a.textContent= -"");Array.isArray(b)||(b=[b]);f(a,a.vnodes,y.normalizeChildren(b),!1,c,null,"http://www.w3.org/1999/xhtml"===e?void 0:e);a.vnodes=b;for(e=0;e