From 354b1a10c1ac7bffb00bf5f5b201ae8f3b64fceb Mon Sep 17 00:00:00 2001 From: suren shrestha Date: Thu, 4 Aug 2016 14:51:14 +0545 Subject: [PATCH 01/19] Update render.md --- docs/render.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/render.md b/docs/render.md index 66f131cf..067c1471 100644 --- a/docs/render.md +++ b/docs/render.md @@ -35,7 +35,7 @@ In contrast, traversing a javascript data structure has a much more predictable ### Differences from other API methods -`m.render()` method is internally called by [`m.mount()`](mount.md), [`m.route()`](route.md), [`m.redraw()`](redraw.md) and `[m.request()](request.md)`. It is not called by [`m.prop()`](prop.md) +`m.render()` method is internally called by [`m.mount()`](mount.md), [`m.route()`](route.md), [`m.redraw()`](redraw.md) and [`m.request()`](request.md). It is not called by [`m.prop()`](prop.md) Unlike with `m.mount()` and `m.route()`, a vnode tree rendered via `m.render()` does not auto-redraw in response to view events, `m.redraw()` calls or `m.request()` calls. It is a low level mechanism suitable for library authors who wish to manually control rendering instead of relying on Mithril's built-in auto-redrawing system. From 88e7cf5454015d88ef0761d49d965a77f7add1aa Mon Sep 17 00:00:00 2001 From: Patrik Johnson Date: Thu, 4 Aug 2016 19:19:12 +0300 Subject: [PATCH 02/19] Rewrite: ignore m.request deserialize option if extract is passed --- docs/request.md | 22 +++++++++++++++++++++- docs/v1.x-migration.md | 2 ++ request/request.js | 2 +- request/tests/test-xhr.js | 21 +++++++++++++++++++-- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/docs/request.md b/docs/request.md index 4fd9a1cf..6ab13da3 100644 --- a/docs/request.md +++ b/docs/request.md @@ -32,7 +32,7 @@ Argument | Type | Required | Descript `options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). `options.serialize` | `string = Function(any)` | No | A serialization method to be applied to `data`. Defaults to `JSON.stringify`, or if `options.data` is an instance of [`FormData`](https://developer.mozilla.org/en/docs/Web/API/FormData), defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function) (i.e. `function(value) {return value}`). `options.deserialize` | `any = Function(string)` | No | A deserialization method to be applied to the response. Defaults to a small wrapper around `JSON.parse` that returns `null` for empty responses. -`options.extract` | `string = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for reading response headers and cookies. Defaults to a function that returns `xhr.responseText` +`options.extract` | `string = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for reading response headers and cookies. Defaults to a function that returns `xhr.responseText`. If defined, `options.deserialize` is ignored. `options.initialValue` | `any` | No | A value to populate the returned stream before the request completes `options.useBody` | `Boolean` | No | Force the use of the HTTP body section for `data` in `GET` requests when set to `true`, or the use of querystring for other HTTP methods when set to `false`. Defaults to `false` for `GET` requests and `true` for other methods. **returns** | `Stream` | | A stream that resolves to the response data, after it has been piped through the `extract`, `deserialize` and `type` methods @@ -378,6 +378,26 @@ Ignoring the fact that the parseCSV function above doesn't handle a lot of cases --- + +### Retrieving response details + +By default Mithril attempts to parse a response as JSON and returns `xhr.responseText`. It may be useful to inspect a server response in more detail, this can be accomplished by passing a custom `options.extract` function: + +```javascript +m.request({ + method: "GET", + url: "/api/v1/users", + extract: function(xhr) {return {status: xhr.status, body: xhr.responseText}} +}) +.run(function(response) { + console.log(response.status, response.body) +}) +``` + +The parameter to `options.extract` is the XMLHttpRequest object once its operation is completed, but before it has been passed to the resulting [stream](prop.md), so the stream may still end up in an errored state if processing throws an exception. + +--- + ### Why JSON instead of HTML Many server-side frameworks provide a view engine that interpolates database data into a template before serving HTML (on page load or via AJAX) and then employ jQuery to handle user interactions. diff --git a/docs/v1.x-migration.md b/docs/v1.x-migration.md index 20cfbeb6..b9f5e8d1 100644 --- a/docs/v1.x-migration.md +++ b/docs/v1.x-migration.md @@ -330,3 +330,5 @@ m.prop.merge([ console.log("Contributors:", users[0].name, "and", users[1].name); }); ``` + +Additionally, if the `extract` option is passed to `m.request` the return value of the provided function will be passed to the [m.prop stream](prop.md) directly, and any `deserialize` callback is ignored. diff --git a/request/request.js b/request/request.js index 01102d3b..7447bb7e 100644 --- a/request/request.js +++ b/request/request.js @@ -38,7 +38,7 @@ module.exports = function($window) { xhr.onreadystatechange = function() { if (xhr.readyState === 4) { try { - var response = args.deserialize(args.extract(xhr, args)) + var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args)) if (xhr.status >= 200 && xhr.status < 300) { stream(cast(args.type, response)) } diff --git a/request/tests/test-xhr.js b/request/tests/test-xhr.js index bfbd1dca..3dc98902 100644 --- a/request/tests/test-xhr.js +++ b/request/tests/test-xhr.js @@ -210,7 +210,7 @@ o.spec("xhr", function() { } }) xhr({method: "GET", url: "/item", extract: extract}).map(function(data) { - o(data).deepEquals({test: 123}) + o(data).equals("{\"test\":123}") }).map(done) }) o("extract parameter works in POST", function(done) { @@ -224,7 +224,24 @@ o.spec("xhr", function() { } }) xhr({method: "POST", url: "/item", extract: extract}).map(function(data) { - o(data).deepEquals({test: 123}) + o(data).equals("{\"test\":123}") + }).map(done) + }) + o("ignores deserialize if extract is defined", function(done) { + var extract = function(data) { + return data.status + } + var deserialize = o.spy() + + mock.$defineRoutes({ + "GET /item": function(request) { + return {status: 200, responseText: ""} + } + }) + xhr({method: "GET", url: "/item", extract: extract, deserialize: deserialize}).map(function(data) { + o(data).equals(200) + }).map(function() { + o(deserialize.callCount).equals(0) }).map(done) }) o("config parameter works", function(done) { From 2e70ca01f814586b311afb7b5ce649681c4e3616 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Thu, 4 Aug 2016 09:58:28 -0700 Subject: [PATCH 03/19] Set up repo for travis commits (#1210) --- .deploy.enc | Bin 0 -> 3248 bytes .travis.yml | 33 +++++++++++++++++++++++++++++++-- package.json | 1 + 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 .deploy.enc diff --git a/.deploy.enc b/.deploy.enc new file mode 100644 index 0000000000000000000000000000000000000000..b22144e36b8024364ee677c2257a42a6a75e5b15 GIT binary patch literal 3248 zcmV;h3{Uehi#D>RW|@a%w|l!pY-hc*WLp0<5|_O~5K`NLa%G9rZ$jTOV{{){$;g!k zun38^_P!5E#iVq`%_)KC(z5mfcP-~mu+dOq_-H^V31AgCBXpu*@Ygrih-{fGe<;6w zY((D3bF--!p``l3k2{0FzKSsR6b|>hU{#dEiN)GJ1z<%s!yNqx&p6Pb&wOAz92+?M zSAv&y5O?|jhGF7Z_0pT+?^p|7F+PA~sZ;l6%k5X00-A0YL905S8N&OPb1v*US3rI) zJV)pBUz=i-=b4!9o)*Qx%1@DEKs-Z|zRrGnV(P{C^RQ(5Ow+CJBU{{nRd8&EP=UFT zeWO(CJG+j1fM~~V-H&exgeY~Hp8lST@fxGIOMM%91g7gxC5r)l(5Jf?Y6t_Xx#uXI zfxf})2XDuh48^THHef3_Qq+tP=|OtP7Av3RI~aA1@LZw1tmyU}7D>llR*5B9+FaMv zs?=t}1Itm*RE~@uC+Apu2h815?@i<`xbjS5V@$3b$bePK?Hfqa^qJ|+(lJt)B+1Dk zEC+Yru~mk{VnGR4+Hj2e=cqrgu;GZnG?}FrIhpq%f=#jRfX>E=Hcx=KP>EH&rB9(3 z6hOEHVQR7yJ^$k6LsnTFk8Pijq_MPjs9u$ATlr?U_0cVw!QSz<65bSSGEbcqi(E0( zXKSRLaIc@9g{0aAr9c?hx-;I)=BTkz&I@2`Q0L}~b?WZWI_O|Q9HmFHMRr zdO|Vimf~>w42;+Wf#NQ@3*C+ddG{6Cx?gN64}$P~;}x*L96&Z;EljH8f6p$wOmt75 zyj}4@Oo(EY4?OECx(95cj&B}mz*H&jw9;}uG^@!<)?@rL^IxI zxI3l##}Y^2`<}T9{+4wUBEj;Iv~)GlEl}f1G_wSw7M-~N;Gw&w>>{JGR#M=K@fc=Q0Te)uV|pAV-z#BNR%~s6W#L;+-y*=={5Cd?>Yxh>r{Nj*>OK=<2uC zvxg@+gk7RW*QeS`gdYWQ+@QOzcKmjyo4YK+1$tE*+l@s*nVh@-?KzG5vk&{BK>H^a z1^tC?)I0eclZMM1^j@SC3q!UyRCvVOOOW5W~q#1;)}IJ$j+!yFVy;~ zaKm7xFROe9azh)hh8OzXO2>MQaf9imcb3~?#Z(_eDy?3b_T%wJ&B-7qcO1_5!die>@ zU~TPl*1Y$kR|WH%B`XcJaEZD8B$``+5HXsn?Xn_)b&a~J3Iu%s(T2!l%zHJS?xUBM zM>K0FX8(J4BlwaLPR<)nm3lyk%0_>c#PLGt2frgNkW7KE;6j-08i`E|d*A?l1b- z0mrpil>OXAbCu0ne;`~mb3glo5`X&x(Eqkp{Mrf(zwdALFZ zTf#qWSWd`pGmMg0M0Cwa&mF*{PY5caYh6MX0k#RAMr)`WTtG??_ZzG%BM(pr3@})u zvWD1m#LIEx5*h-6`oZ~IR>^$3yc-;QoG(u8Q1@N3twWx1TV0bRNyr@G%EE|coV&KJ zM*&O-O?Hy?(<&KwZM0cA8K-gvj{yOpYWykkj^1PM_J0-T93Q3_fWNW?PB^f=P{-ec zW%K#KBGrLjT4Iqq|y4xMhOq!4pXz%h?33u zWo4lt#emfflhVaVdaxYXna2)`R2dpgqvQ~mX3Kg*HCUQTWAM{4SR}hFZ=J?sF2_|* zHw?ax{KnQpP$cPfV~qa4f1!Ny0dx_b38Ue06dXy6bW4#Xkh=zeae#pJb_l?1g8)Pj zVmDa@uITXxQw+ri%xqU_>SSg$qa41049=#QyI>E~4&fRivRh+6>EF1^>6U0aIR99x zjiF~qKgagBusTFKo6*d!Uzz}*C!i163NFt7NKaO!#qkt(LELnpN09FT4i22 zLfP+6ht0=x&~5rW>Ceh1|&m8Cyq!jOvLlX@ILSDO~Xw-bWN_MJWCVcJ65S=#-Y zgCx4SLJWsVQcUos8E?#DWp(mH?n#|}yQ%-S2)fEbq+hPRUT*LTP)6cCtTSstA&CQ# zYb=hs;oYB{MnA045%^@fB;=BH@S3V}fs%OKRq(9h1DmCZ&_qHko$6B!nB*y+C>zQS$MAW;iv<2SDi;$ zf%s~N<(7%Ai_n1RK`hFo1H1nOxIY|Q(zGb{!1Bsy@Qssr+b|M1#TgL!m~M3knRJ_e zPi>f7D{zVBK6a)XOP1VVYJ8>X!u9j+U($OT;omz*0Oob=Nw(rv5u+37WY6Hr8jRrP zm5=Kb@b0jqu^M|9zc$Grld)gq%JVYuM0@tv6rp zS^(r76~;ghq37=ED5;P}BYpPX$q&xsCCt$9x*W@WeZg=lwl3iyI?NT7Yy>W)X^E;8 zSqm%NUwZ`md||JqLc91nh*lm@dq3ltjjCRihlTUKXhs2_bwc;6}swj;z(?w1#qc?5ureyrOkh& z!_tV_ByXFGh~sYP0w7qYghC5~BKhiz{s3G&q`WpILd!K+AvUjSmYo5QmzC1E-ANWh zodWmbc><04LQfqGNOi`WbrojRYrMoP20|%pmEy*FFUqKGpaBcGpU8OJH{&I{kMvW; zT1RS<2C6SYT?lkyS~>vpcqIK2;Us{v#C@*bauD-^TPOdt3)5d&`_08!zSEUZX$V+XFPpS+Mq?F<*P*)yX*TaSPaHrk;J0MbjAf iq@rT?Z_QaI!VZn<({JW6-tuX$P8b&*P&B~vhNKe_)@rr@ literal 0 HcmV?d00001 diff --git a/.travis.yml b/.travis.yml index 2b42d205..c6b34207 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,34 @@ +sudo: false + language: node_js node_js: - - stable +- node -sudo: false +cache: + directories: + - node_modules + +after_success: + - | + + # Only want to do this on commits to $BRANCH + [ "$TRAVIS_PULL_REQUEST" == "true" -o "$TRAVIS_BRANCH" != "$BRANCH" ] && (echo Artifacts only built on $BRANCH; exit 0) + + # Set up SSH environment + $(npm bin)/set-up-ssh --key "$encrypted_8b86e0359d64_key" \ + --iv "$encrypted_8b86e0359d64_iv" \ + --path-encrypted-key "./.deploy.enc" + + # Build & commit changes + $(npm bin)/commit-changes --commands "npm run build" \ + --commit-message "Update bundles [skip ci]" \ + --branch "$BRANCH" + +env: + global: + # Restrict the branch this will activate on + - BRANCH=rewrite + + # Set up GH_USER_EMAIL & GH_USER_NAME env variables used by travis-scripts package + - secure: Xvqvm3+PvJu/rs3jl/NNn0RWLkkLkIoPHiL0GCfVRaywgjCYVN02g54NVvIDaOfybqPmu9E6PJFVs92vhF34NMFQHf4EWskynusIGV271R2BV0i+OJBfLMuLgiwm6zRn7/Zw4JvWIUGEwcnlz0qxbqdHsS0SOR3fIkFzePickW0= + - secure: Rf/ldEO9d4vItJhe6EmqWpFAyCARzoCb422nHnjr1hYJknnwIXpgyZ1C/7On/9o7rWPPf+8WcHC/rgjK2rthKCldzdG5I60LfWSNzap9lk3Aa4TpSCoDBuEp7JVvDr5tc3rKnBXVT71hOay7RSx1StWzXiJs9mjaeVMJzYzRT78= diff --git a/package.json b/package.json index 14d8f92a..708ae75d 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "cover": "istanbul cover --print both ospec/bin/ospec" }, "devDependencies": { + "@alrra/travis-scripts": "^3.0.1", "eslint": "^2.10.2", "istanbul": "^0.4.3" }, From 85ecd3545fed01da8ca1473e8aea6547d12609d6 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Thu, 4 Aug 2016 17:24:38 +0000 Subject: [PATCH 04/19] Update bundles [skip ci] --- mithril.js | 2 +- mithril.min.js | 74 +++++++++++++++++++++++++------------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/mithril.js b/mithril.js index 869da60a..19a6bafe 100644 --- a/mithril.js +++ b/mithril.js @@ -808,7 +808,7 @@ var requestService = function($window) { xhr.onreadystatechange = function() { if (xhr.readyState === 4) { try { - var response = args.deserialize(args.extract(xhr, args)) + var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args)) if (xhr.status >= 200 && xhr.status < 300) { stream(cast(args.type, response)) } diff --git a/mithril.min.js b/mithril.min.js index 3b2074e8..2318a012 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,40 +1,40 @@ -new function(){(function(){function y(){function a(){0=q&&z>=A;){var t=c[q],w=f[A];if(t===w)q++,A++;else if(null!=t&&null!=w&&t.key===w.key)q++,A++,k(a,t,w,d,e(c,q,g),p,B),p&&t.tag===w.tag&&n(a,m(t),g);else if(t=c[r],t===w)r--,A++;else if(null!=t&&null!=w&&t.key===w.key)k(a,t,w,d,e(c,r+1,g),p,B),n(a,m(t),e(c,q,g)),r--,A++;else break}for(;r>=q&&z>=A;){t=c[r];w=f[z]; -if(t===w)r--;else if(null!=t&&null!=w&&t.key===w.key)k(a,t,w,d,e(c,r+1,g),p,B),p&&t.tag===w.tag&&n(a,m(t),g),null!=t.dom&&(g=t.dom),r--;else{if(!u){u=c;var t=r,l={},x;for(x=0;xa.indexOf("?")?"?":"&";a+=g+d}return a}function d(a){try{return""!==a?JSON.parse(a):null}catch(b){throw Error(a);}}function g(a){return a.responseText}var k=0,m;return{xhr:function(e){var n=C.stream();void 0!==e.initialValue&&n(e.initialValue);var v="boolean"===typeof e.useBody?e.useBody:"GET"!==e.method&& -"TRACE"!==e.method;"function"!==typeof e.serialize&&(e.serialize="undefined"!==typeof FormData&&e.data instanceof FormData?function(a){return a}:JSON.stringify);"function"!==typeof e.deserialize&&(e.deserialize=d);"function"!==typeof e.extract&&(e.extract=g);e.url=b(e.url,e.data);v?e.data=e.serialize(e.data):e.url=h(e.url,e.data);var k=new a.XMLHttpRequest;k.open(e.method,e.url,"boolean"===typeof e.async?e.async:!0,"string"===typeof e.user?e.user:void 0,"string"===typeof e.password?e.password:void 0); -e.serialize===JSON.stringify&&v&&k.setRequestHeader("Content-Type","application/json; charset=utf-8");e.deserialize===d&&k.setRequestHeader("Accept","application/json, text/*");"function"===typeof e.config&&(k=e.config(k,e)||k);k.onreadystatechange=function(){if(4===k.readyState){try{var a=e.deserialize(e.extract(k,e));if(200<=k.status&&300>k.status){if("function"===typeof e.type)if(a instanceof Array)for(var b=0;b=r&&B>=A;){var u=c[r],w=f[A];if(u===w)r++,A++;else if(null!=u&&null!=w&&u.key===w.key)r++,A++,p(a,u,w,d,m(c,r,g),z,k),z&&u.tag===w.tag&&e(a,l(u),g);else if(u=c[v],u===w)v--,A++;else if(null!=u&&null!=w&&u.key===w.key)p(a,u,w,d,m(c,v+1,g),z,k),e(a,l(u),m(c,r,g)),v--,A++;else break}for(;v>=r&&B>=A;){u=c[v];w=f[B]; +if(u===w)v--;else if(null!=u&&null!=w&&u.key===w.key)p(a,u,w,d,m(c,v+1,g),z,k),z&&u.tag===w.tag&&e(a,l(u),g),null!=u.dom&&(g=u.dom),v--;else{if(!n){n=c;var u=v,t={},x;for(x=0;xa.indexOf("?")?"?":"&";a+=g+d}return a}function d(a){try{return""!==a?JSON.parse(a):null}catch(b){throw Error(a);}}function g(a){return a.responseText}function p(a,b){if("function"===typeof a)if(b instanceof Array)for(var d=0;dk.status)q(p(e.type, +a));else{var b=Error(k.responseText),d;for(d in a)b[d]=a[d];q.error(b)}}catch(h){q.error(h)}"function"===typeof m&&m()}};l?k.send(e.data):k.send();return q},jsonp:function(e){var d=C.stream();void 0!==e.initialValue&&d(e.initialValue);var g=e.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=a.document.createElement("script");a[g]=function(b){k.parentNode.removeChild(k);d(p(e.type,b));"function"===typeof m&&m();delete a[g]};k.onerror=function(){k.parentNode.removeChild(k);d.error(Error("JSONP request failed")); +"function"===typeof m&&m();delete a[g]};null==e.data&&(e.data={});e.url=b(e.url,e.data);e.data[e.callbackKey||"callback"]=g;k.src=h(e.url,e.data);a.document.documentElement.appendChild(k);return d},setCompletionCallback:function(a){m=a}}}(window),H=function(){var a=[];return{subscribe:a.push.bind(a),unsubscribe:function(b){b=a.indexOf(b);-1 Date: Thu, 4 Aug 2016 11:24:32 -0700 Subject: [PATCH 05/19] Move travis-scripts to be build-server only (#1215) * Move travis-scripts to be build-server only * Fix up conditional logic --- .travis.yml | 13 +++++++++++-- package.json | 1 - 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c6b34207..bb2c286c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,20 @@ cache: directories: - node_modules +# Custom install step so the travis scripts don't need to be in package.json +install: + - npm install + - npm install @alrra/travis-scripts@^3.0.1 + +# After a successful build create bundles & commit back to the repo after_success: - | - # Only want to do this on commits to $BRANCH - [ "$TRAVIS_PULL_REQUEST" == "true" -o "$TRAVIS_BRANCH" != "$BRANCH" ] && (echo Artifacts only built on $BRANCH; exit 0) + # Only want to commit things on commits to $BRANCH + if [ "$TRAVIS_EVENT_TYPE" == "pull_request" ] || [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then + echo "Artifacts only built on $BRANCH" + exit 0 + fi # Set up SSH environment $(npm bin)/set-up-ssh --key "$encrypted_8b86e0359d64_key" \ diff --git a/package.json b/package.json index 708ae75d..14d8f92a 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "cover": "istanbul cover --print both ospec/bin/ospec" }, "devDependencies": { - "@alrra/travis-scripts": "^3.0.1", "eslint": "^2.10.2", "istanbul": "^0.4.3" }, From 37e7b67a492060eed819371f0f0dac72855dba5d Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Sun, 24 Jul 2016 14:09:33 +0200 Subject: [PATCH 06/19] Add a test for unmounting. --- api/tests/test-mount.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/api/tests/test-mount.js b/api/tests/test-mount.js index 6baeea67..ee383c65 100644 --- a/api/tests/test-mount.js +++ b/api/tests/test-mount.js @@ -32,6 +32,20 @@ o.spec("mount", function() { o(root.firstChild.nodeName).equals("DIV") }) + o("mounting null deletes `redraw` from `root`", function() { + mount(root, { + view : function() { + return m("div") + } + }) + + o(typeof root.redraw).equals('function') + + mount(root, null) + + o(typeof root.redraw).equals('undefined') + }) + o("redraws on events", function(done) { var onupdate = o.spy() var oninit = o.spy() From ccc2e1c85d81c8e764383f59fb30d8115ab4627d Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Sun, 24 Jul 2016 14:10:30 +0200 Subject: [PATCH 07/19] Unmount and cleanly refresh mount points. --- api/mount.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/api/mount.js b/api/mount.js index 5f821b7c..cc16c81f 100644 --- a/api/mount.js +++ b/api/mount.js @@ -1,14 +1,26 @@ "use strict" +var Vnode = require("../render/vnode") var coreRenderer = require("../render/render") var autoredraw = require("../api/autoredraw") +var dummy = {view: function() {}} module.exports = function(renderer, pubsub) { return function(root, component) { + pubsub.unsubscribe(root.redraw) + var run = autoredraw(root, renderer, pubsub, function() { - renderer.render(root, {tag: component}) + renderer.render( + root, + Vnode(component === null ? dummy : component, undefined, undefined, undefined, undefined, undefined) + ) }) run() + + if (component === null) { + pubsub.unsubscribe(root.redraw) + delete root.redraw + } } } From 8c1e0cc0733f2962849eeaf38c64329e1d1e775f Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Sun, 24 Jul 2016 14:53:31 +0200 Subject: [PATCH 08/19] m.mount() docs update. --- docs/mount.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/mount.md b/docs/mount.md index 95540b06..a402e1e6 100644 --- a/docs/mount.md +++ b/docs/mount.md @@ -2,6 +2,7 @@ - [API](#api) - [How it works](#how-it-works) +- [Performance considerations](#performance-considerations) - [Differences from m.render](#differences-from-m-render) --- @@ -13,7 +14,7 @@ Argument | Type | Required | Description ----------- | -------------------- | -------- | --- `element` | `Element` | Yes | A DOM element that will be the parent node to the subtree -`component` | `Component` | Yes | The [component](components.md) to be rendered +`component` | `Component|null` | Yes | The [component](components.md) to be rendered. `null` unmounts the tree and cleans up internal state. **returns** | | | Returns nothing [How to read signatures](signatures.md) @@ -24,6 +25,18 @@ Argument | Type | Required | Description Similar to [`m.render()`](render.md), the `m.mount()` method takes a component and mounts a corresponding DOM tree into `element`. If `element` already has a DOM tree mounted via a previous `m.mount()` call, the component is diffed against the previous vnode tree and the existing DOM tree is modified only where needed to reflect the changes. Unchanged DOM nodes are not touched at all. +#### Replace a component + +Running `mount(element, OtherComponent)` where `element` is a current mount point replaces the component previously mounted with `OtherComponent`. + +#### Unmount + +Using `m.mount(element, null)` on an element with a previously mounted component unmounts it and cleans up Mithril internal state. This can be useful to prevent memory leaks when removing the `root` node manually from the DOM. + +--- + +### Performance considerations + It may seem wasteful to generate a vnode tree on every redraw, but as it turns out, creating and comparing Javascript data structures is surprisingly cheap compared to reading and modifying the DOM. Touching the DOM can be extremely expensive for a couple of reasons. Alternating reads and writes can adversely affect performance by causing several browser repaints to occur in quick succession, whereas comparing virtual dom trees allows writes to be batched into a single repaint. Also, the performance characteristics of various DOM operations vary between implementations and can be difficult to learn and optimize for all browsers. For example, in some implementations, reading `childNodes.length` has a complexity of O(n); in some, reading `parentNode` causes a repaint, etc. From 7c0223ec09f5635bcfd900b42aab7d1b4835cd51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Yves=20G=C3=A9rardy?= Date: Fri, 5 Aug 2016 01:55:26 +0200 Subject: [PATCH 09/19] Migration docs: m.mount and m.route now require components where vdom nodes were once tolerated (#1217) --- docs/v1.x-migration.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/v1.x-migration.md b/docs/v1.x-migration.md index b9f5e8d1..11a6b2c9 100644 --- a/docs/v1.x-migration.md +++ b/docs/v1.x-migration.md @@ -7,6 +7,7 @@ - [Component `controller` function](#component-controller-function) - [Component arguments](#component-arguments) - [Passing components to `m()`](#passing-components-to-m) +- [Passing vnodes to `m.mount()` and `m.route()`](#passing-vnodes-to-mmount-and-mroute) - [`m.route` mode](#mroute-mode) - [`m.route` and anchor tags](#mroute-and-anchor-tags) - [Reading/writing the current route](#readingwriting-the-current-route) @@ -178,6 +179,34 @@ m("div", component); m("div", m(component)); ``` +## Passing vnodes to `m.mount()` and `m.route()` + +In `v0.2.x`, `m.mount(element, component)` tolerated [vnodes](vnodes.md) as second arguments instead of [components](components.md) (even though it wasn't documented). Likewise, `m.route(element, defaultRoute, routes)` accepted vnodes as values in the `routes` object. + +In `v1.x`, components are required instead in both cases. + +### `v0.2.x` + +```javascript +m.mount(element, m('i', 'hello')); +m.mount(element, m(Component, attrs)); + +m.route(element, '/', { + '/': m('b', 'bye') +}) +``` + +### `v1.x` + +```javascript +m.mount(element, {view: function () {return m('i', 'hello')}}); +m.mount(element, {view: function () {return m(Component, attrs)}}); + +m.route(element, '/', { + '/': {view: function () {return m('b', 'bye')}} +}) +``` + ## `m.route` mode `m.route.mode` was replaced by `m.route.prefix(prefix)` where `prefix` can be `#`, `?`, `` (for "pathname" mode). The new API also supports hashbang (`#!`), which is the default, and it supports non-root pathnames and arbitrary mode variations such as querybang (`?!`) From 2048633d5d00bf43de5cf4d1c880d80452e138e5 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Fri, 5 Aug 2016 02:47:46 +0000 Subject: [PATCH 10/19] Update bundles [skip ci] --- mithril.js | 11 ++++++++- mithril.min.js | 60 +++++++++++++++++++++++++------------------------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/mithril.js b/mithril.js index 19a6bafe..51a68fbb 100644 --- a/mithril.js +++ b/mithril.js @@ -1103,12 +1103,21 @@ m.route = function($window, renderer, pubsub) { return route }(window, renderService, redrawService) +var dummy = {view: function() {}} m.mount = function(renderer, pubsub) { return function(root, component) { + pubsub.unsubscribe(root.redraw) var run = autoredraw(root, renderer, pubsub, function() { - renderer.render(root, {tag: component}) + renderer.render( + root, + Vnode(component === null ? dummy : component, undefined, undefined, undefined, undefined, undefined) + ) }) run() + if (component === null) { + pubsub.unsubscribe(root.redraw) + delete root.redraw + } } }(renderService, redrawService) m.trust = function(html) { diff --git a/mithril.min.js b/mithril.min.js index 2318a012..2debfa92 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -4,37 +4,37 @@ h&&"function"===typeof a._state.recover)try{var d=a._state.recover();if(d===F)re void 0,p),T(a,p)}}function S(a,b){null!=a&&a.constructor===y&&(b=void 0!==a._state.error?a._state.error:S(a._state.value,a._state.error));return b}function T(a,b){0===Object.keys(a._state.deps).length&&setTimeout(function(){0===Object.keys(a._state.deps).length&&console.error(b)},0)}function ea(a){var b=y(),h=this;return D(b,[h],function(){return W(b,a(h()))},void 0)}function fa(a){var b=y(),h=this;return D(b,[h],function(){return h._state.value},function(){return W(b,a(h._state.error))})}function G(a, b){return D(y(),b,function(){var h=b.filter(V);if(0=r&&B>=A;){var u=c[r],w=f[A];if(u===w)r++,A++;else if(null!=u&&null!=w&&u.key===w.key)r++,A++,p(a,u,w,d,m(c,r,g),z,k),z&&u.tag===w.tag&&e(a,l(u),g);else if(u=c[v],u===w)v--,A++;else if(null!=u&&null!=w&&u.key===w.key)p(a,u,w,d,m(c,v+1,g),z,k),e(a,l(u),m(c,r,g)),v--,A++;else break}for(;v>=r&&B>=A;){u=c[v];w=f[B]; -if(u===w)v--;else if(null!=u&&null!=w&&u.key===w.key)p(a,u,w,d,m(c,v+1,g),z,k),z&&u.tag===w.tag&&e(a,l(u),g),null!=u.dom&&(g=u.dom),v--;else{if(!n){n=c;var u=v,t={},x;for(x=0;xa.indexOf("?")?"?":"&";a+=g+d}return a}function d(a){try{return""!==a?JSON.parse(a):null}catch(b){throw Error(a);}}function g(a){return a.responseText}function p(a,b){if("function"===typeof a)if(b instanceof Array)for(var d=0;dk.status)q(p(e.type, -a));else{var b=Error(k.responseText),d;for(d in a)b[d]=a[d];q.error(b)}}catch(h){q.error(h)}"function"===typeof m&&m()}};l?k.send(e.data):k.send();return q},jsonp:function(e){var d=C.stream();void 0!==e.initialValue&&d(e.initialValue);var g=e.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=a.document.createElement("script");a[g]=function(b){k.parentNode.removeChild(k);d(p(e.type,b));"function"===typeof m&&m();delete a[g]};k.onerror=function(){k.parentNode.removeChild(k);d.error(Error("JSONP request failed")); -"function"===typeof m&&m();delete a[g]};null==e.data&&(e.data={});e.url=b(e.url,e.data);e.data[e.callbackKey||"callback"]=g;k.src=h(e.url,e.data);a.document.documentElement.appendChild(k);return d},setCompletionCallback:function(a){m=a}}}(window),H=function(){var a=[];return{subscribe:a.push.bind(a),unsubscribe:function(b){b=a.indexOf(b);-1=r&&B>=A;){var t=c[r],w=e[A];if(t===w)r++,A++;else if(null!=t&&null!=w&&t.key===w.key)r++,A++,p(a,t,w,d,m(c,r,g),z,k),z&&t.tag===w.tag&&f(a,l(t),g);else if(t=c[v],t===w)v--,A++;else if(null!=t&&null!=w&&t.key===w.key)p(a,t,w,d,m(c,v+1,g),z,k),f(a,l(t),m(c,r,g)),v--,A++;else break}for(;v>=r&&B>=A;){t=c[v];w=e[B]; +if(t===w)v--;else if(null!=t&&null!=w&&t.key===w.key)p(a,t,w,d,m(c,v+1,g),z,k),z&&t.tag===w.tag&&f(a,l(t),g),null!=t.dom&&(g=t.dom),v--;else{if(!u){u=c;var t=v,n={},x;for(x=0;xa.indexOf("?")?"?":"&";a+=g+d}return a}function d(a){try{return""!==a?JSON.parse(a):null}catch(b){throw Error(a);}}function g(a){return a.responseText}function p(a,b){if("function"===typeof a)if(b instanceof Array)for(var d=0;dk.status)q(p(f.type, +a));else{var b=Error(k.responseText),d;for(d in a)b[d]=a[d];q.error(b)}}catch(h){q.error(h)}"function"===typeof m&&m()}};l?k.send(f.data):k.send();return q},jsonp:function(f){var d=C.stream();void 0!==f.initialValue&&d(f.initialValue);var g=f.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=a.document.createElement("script");a[g]=function(b){k.parentNode.removeChild(k);d(p(f.type,b));"function"===typeof m&&m();delete a[g]};k.onerror=function(){k.parentNode.removeChild(k);d.error(Error("JSONP request failed")); +"function"===typeof m&&m();delete a[g]};null==f.data&&(f.data={});f.url=b(f.url,f.data);f.data[f.callbackKey||"callback"]=g;k.src=h(f.url,f.data);a.document.documentElement.appendChild(k);return d},setCompletionCallback:function(a){m=a}}}(window),H=function(){var a=[];return{subscribe:a.push.bind(a),unsubscribe:function(b){b=a.indexOf(b);-1 Date: Thu, 4 Aug 2016 22:38:20 -0700 Subject: [PATCH 11/19] Improve travis commit message slightly (#1218) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bb2c286c..5cac2607 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ after_success: # Build & commit changes $(npm bin)/commit-changes --commands "npm run build" \ - --commit-message "Update bundles [skip ci]" \ + --commit-message "Bundled output for commit $TRAVIS_COMMIT [skip ci]" \ --branch "$BRANCH" env: From b0848070bcd237ecbddee216e0d96461dd720a19 Mon Sep 17 00:00:00 2001 From: Gilbert Date: Fri, 5 Aug 2016 00:49:26 -0500 Subject: [PATCH 12/19] Overwrite an element's style using cssText This is the proper way to do it: https://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-cssText --- render/render.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/render/render.js b/render/render.js index 50d2404c..906291cb 100644 --- a/render/render.js +++ b/render/render.js @@ -441,11 +441,11 @@ module.exports = function($window) { //style function updateStyle(element, old, style) { - if (old === style) element.style = "", old = null - if (style == null) element.style = "" - else if (typeof style === "string") element.style = style + if (old === style) element.cssText = "", old = null + if (style == null) element.cssText = "" + else if (typeof style === "string") element.cssText = style else { - if (typeof old === "string") element.style = "" + if (typeof old === "string") element.cssText = "" for (var key in style) { element.style[key] = style[key] } From 5fd2e99831bbad04a4dcd9a3d8c992ebc1bf73c2 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Fri, 5 Aug 2016 12:38:31 +0200 Subject: [PATCH 13/19] Fix the domMock and domMock tests for cssText support --- test-utils/domMock.js | 6 +++++- test-utils/tests/test-domMock.js | 20 +++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/test-utils/domMock.js b/test-utils/domMock.js index 0af6b252..cde58a24 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -136,7 +136,11 @@ module.exports = function() { get style() { return style }, - set style(value) { + set style(_){ + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style#Setting_style + throw new Error("setting element.style is not portable") + }, + set cssText(value) { if (typeof value === "string") { for (var key in style) style[key] = "" var rules = value.split(";") diff --git a/test-utils/tests/test-domMock.js b/test-utils/tests/test-domMock.js index 527b4e61..2b589673 100644 --- a/test-utils/tests/test-domMock.js +++ b/test-utils/tests/test-domMock.js @@ -474,20 +474,30 @@ o.spec("domMock", function() { o(typeof div.style).equals("object") }) - o("setting style string works", function() { + o("setting cssText string works", function() { var div = $document.createElement("div") - div.style = "background-color: red; border-bottom: 1px solid red;" + div.cssText = "background-color: red; border-bottom: 1px solid red;" o(div.style.backgroundColor).equals("red") o(div.style.borderBottom).equals("1px solid red") }) - o("removing via setting style string works", function() { + o("removing via setting cssText string works", function() { var div = $document.createElement("div") - div.style = "background: red;" - div.style = "" + div.cssText = "background: red;" + div.cssText = "" o(div.style.background).equals("") }) + o("setting style throws", function () { + var err = false + try { + div.style = '' + } catch (e) { + err = e + } + + o(err instanceof Error).equals(true) + }) }) o.spec("events", function() { o.spec("click", function() { From 5eb9c9010bcc305f850fbcfa85ce0b66e2b13ca3 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Fri, 5 Aug 2016 13:46:28 +0000 Subject: [PATCH 14/19] Bundled output for commit 88d56cd4ae67a07f0fc33ec2efe87b4a0cdd40dd [skip ci] --- mithril.js | 8 ++++---- mithril.min.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mithril.js b/mithril.js index 51a68fbb..08586738 100644 --- a/mithril.js +++ b/mithril.js @@ -675,11 +675,11 @@ var renderService = function($window) { } //style function updateStyle(element, old, style) { - if (old === style) element.style = "", old = null - if (style == null) element.style = "" - else if (typeof style === "string") element.style = style + if (old === style) element.cssText = "", old = null + if (style == null) element.cssText = "" + else if (typeof style === "string") element.cssText = style else { - if (typeof old === "string") element.style = "" + if (typeof old === "string") element.cssText = "" for (var key in style) { element.style[key] = style[key] } diff --git a/mithril.min.js b/mithril.min.js index 2debfa92..83a9f417 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -21,7 +21,7 @@ u.normalize(e.tag.view.call(e.state,e)),y(e.tag,e,b,q),null!=e.instance?(null==c e.firstChild)}return e}return a.dom}function m(a,c,e){for(;c Date: Fri, 5 Aug 2016 14:03:34 -0700 Subject: [PATCH 15/19] render clears non-mithril dom nodes --- api/mount.js | 1 - render/render.js | 7 ++++++- render/tests/index.html | 1 + render/tests/test-render.js | 24 ++++++++++++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 render/tests/test-render.js diff --git a/api/mount.js b/api/mount.js index cc16c81f..0465c4c7 100644 --- a/api/mount.js +++ b/api/mount.js @@ -1,7 +1,6 @@ "use strict" var Vnode = require("../render/vnode") -var coreRenderer = require("../render/render") var autoredraw = require("../api/autoredraw") var dummy = {view: function() {}} diff --git a/render/render.js b/render/render.js index 906291cb..5a83c388 100644 --- a/render/render.js +++ b/render/render.js @@ -514,7 +514,12 @@ module.exports = function($window) { function render(dom, vnodes) { var hooks = [] var active = $doc.activeElement - if (dom.vnodes == null) dom.vnodes = [] + + // First time rendering into a node clears it out + if (dom.vnodes == null) { + dom.vnodes = [] + dom.textContent = ""; + } if (!(vnodes instanceof Array)) vnodes = [vnodes] updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined) diff --git a/render/tests/index.html b/render/tests/index.html index 9c58d56b..2b4e8542 100644 --- a/render/tests/index.html +++ b/render/tests/index.html @@ -38,6 +38,7 @@ + diff --git a/render/tests/test-render.js b/render/tests/test-render.js new file mode 100644 index 00000000..60823166 --- /dev/null +++ b/render/tests/test-render.js @@ -0,0 +1,24 @@ +"use strict" + +var o = require("../../ospec/ospec") +var domMock = require("../../test-utils/domMock") +var vdom = require("../../render/render") + +o.spec("render", function() { + var $window, root, render + o.beforeEach(function() { + $window = domMock() + root = $window.document.createElement("div") + render = vdom($window).render + }) + + o("overwrites existing content", function() { + var vnodes = [{tag: "a", text: null}] + + root.appendChild($window.document.createElement("div")); + + render(root, vnodes) + + o(root.childNodes.length).equals(1) + }) +}) From 27b1fdabedd370310a3a46189eb4cce5fb761cad Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Fri, 5 Aug 2016 21:28:05 +0000 Subject: [PATCH 16/19] Bundled output for commit 06d5a238d432c9f70330532ad1d36ac1eb7713fb [skip ci] --- mithril.js | 7 ++++++- mithril.min.js | 30 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/mithril.js b/mithril.js index 08586738..77e103c2 100644 --- a/mithril.js +++ b/mithril.js @@ -744,7 +744,12 @@ var renderService = function($window) { function render(dom, vnodes) { var hooks = [] var active = $doc.activeElement - if (dom.vnodes == null) dom.vnodes = [] + + // First time rendering into a node clears it out + if (dom.vnodes == null) { + dom.vnodes = [] + dom.textContent = ""; + } if (!(vnodes instanceof Array)) vnodes = [vnodes] updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined) dom.vnodes = vnodes diff --git a/mithril.min.js b/mithril.min.js index 83a9f417..4cef2674 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -23,18 +23,18 @@ c.tag.onbeforeremove.call(c.state,c,b));if(0a.indexOf("?")?"?":"&";a+=g+d}return a}function d(a){try{return""!==a?JSON.parse(a):null}catch(b){throw Error(a);}}function g(a){return a.responseText}function p(a,b){if("function"===typeof a)if(b instanceof Array)for(var d=0;dk.status)q(p(f.type, -a));else{var b=Error(k.responseText),d;for(d in a)b[d]=a[d];q.error(b)}}catch(h){q.error(h)}"function"===typeof m&&m()}};l?k.send(f.data):k.send();return q},jsonp:function(f){var d=C.stream();void 0!==f.initialValue&&d(f.initialValue);var g=f.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=a.document.createElement("script");a[g]=function(b){k.parentNode.removeChild(k);d(p(f.type,b));"function"===typeof m&&m();delete a[g]};k.onerror=function(){k.parentNode.removeChild(k);d.error(Error("JSONP request failed")); -"function"===typeof m&&m();delete a[g]};null==f.data&&(f.data={});f.url=b(f.url,f.data);f.data[f.callbackKey||"callback"]=g;k.src=h(f.url,f.data);a.document.documentElement.appendChild(k);return d},setCompletionCallback:function(a){m=a}}}(window),H=function(){var a=[];return{subscribe:a.push.bind(a),unsubscribe:function(b){b=a.indexOf(b);-1a.indexOf("?")?"?":"&";a+=g+d}return a}function d(a){try{return""!==a?JSON.parse(a):null}catch(b){throw Error(a);}}function g(a){return a.responseText}function p(a,b){if("function"===typeof a)if(b instanceof Array)for(var d=0;dk.status)q(p(f.type,a));else{var b=Error(k.responseText),d;for(d in a)b[d]=a[d];q.error(b)}}catch(h){q.error(h)}"function"===typeof m&&m()}};l?k.send(f.data):k.send();return q},jsonp:function(f){var d=C.stream();void 0!==f.initialValue&&d(f.initialValue);var g=f.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=a.document.createElement("script");a[g]=function(b){k.parentNode.removeChild(k);d(p(f.type,b));"function"===typeof m&&m();delete a[g]};k.onerror= +function(){k.parentNode.removeChild(k);d.error(Error("JSONP request failed"));"function"===typeof m&&m();delete a[g]};null==f.data&&(f.data={});f.url=b(f.url,f.data);f.data[f.callbackKey||"callback"]=g;k.src=h(f.url,f.data);a.document.documentElement.appendChild(k);return d},setCompletionCallback:function(a){m=a}}}(window),H=function(){var a=[];return{subscribe:a.push.bind(a),unsubscribe:function(b){b=a.indexOf(b);-1 Date: Fri, 5 Aug 2016 14:49:16 -0700 Subject: [PATCH 17/19] Clean up test for #1222 a bit (#1223) Since @lhorie asked nicely. --- render/tests/test-render.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/render/tests/test-render.js b/render/tests/test-render.js index 60823166..f23c9565 100644 --- a/render/tests/test-render.js +++ b/render/tests/test-render.js @@ -13,12 +13,12 @@ o.spec("render", function() { }) o("overwrites existing content", function() { - var vnodes = [{tag: "a", text: null}] + var vnodes = [] root.appendChild($window.document.createElement("div")); render(root, vnodes) - o(root.childNodes.length).equals(1) + o(root.childNodes.length).equals(0) }) }) From b6bb63ff85726c649b5317da895e7c7763c0282b Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Sat, 6 Aug 2016 00:09:48 +0200 Subject: [PATCH 18/19] Skip diff for cached, non-keyed nodes fix #1206 --- render/render.js | 4 ++-- render/tests/test-updateNodes.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/render/render.js b/render/render.js index 5a83c388..bc224acf 100644 --- a/render/render.js +++ b/render/render.js @@ -109,7 +109,7 @@ module.exports = function($window) { //update function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { - if (old == null && vnodes == null) return + if (old === vnodes || old == null && vnodes == null) return else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined) else if (vnodes == null) removeNodes(parent, old, 0, old.length, vnodes) else { @@ -118,7 +118,7 @@ module.exports = function($window) { if (old.length === vnodes.length && vnodes[0] != null && vnodes[0].key == null) { for (var i = 0; i < old.length; i++) { - if (old[i] == null && vnodes[i] == null) continue + if (old[i] === vnodes[i] || old[i] == null && vnodes[i] == null) continue else if (old[i] == null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling)) else if (vnodes[i] == null) removeNodes(parent, old, i, i + 1, vnodes) else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns) diff --git a/render/tests/test-updateNodes.js b/render/tests/test-updateNodes.js index a3c39394..b8468bc8 100644 --- a/render/tests/test-updateNodes.js +++ b/render/tests/test-updateNodes.js @@ -845,4 +845,22 @@ o.spec("updateNodes", function() { o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[1].nodeName).equals("S") }) + o("cached, non-keyed nodes skip diff", function () { + var onupdate = o.spy(); + var cached = {tag:"a", attrs:{onupdate: onupdate}} + + render(root, cached) + render(root, cached) + + o(onupdate.callCount).equals(0) + }) + o("cached, keyed nodes skip diff", function () { + var onupdate = o.spy(); + var cached = {tag:"a", key:"a", attrs:{onupdate: onupdate}} + + render(root, cached) + render(root, cached) + + o(onupdate.callCount).equals(0) + }) }) From c263cf2494c252358a597d0f605a27410915b5c6 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Sat, 6 Aug 2016 13:57:15 +0000 Subject: [PATCH 19/19] Bundled output for commit 50a80e55905975c9104b15c9139755fbd0964630 [skip ci] --- mithril.js | 4 ++-- mithril.min.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mithril.js b/mithril.js index 77e103c2..9c83ef6b 100644 --- a/mithril.js +++ b/mithril.js @@ -348,7 +348,7 @@ var renderService = function($window) { } //update function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { - if (old == null && vnodes == null) return + if (old === vnodes || old == null && vnodes == null) return else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined) else if (vnodes == null) removeNodes(parent, old, 0, old.length, vnodes) else { @@ -357,7 +357,7 @@ var renderService = function($window) { if (old.length === vnodes.length && vnodes[0] != null && vnodes[0].key == null) { for (var i = 0; i < old.length; i++) { - if (old[i] == null && vnodes[i] == null) continue + if (old[i] === vnodes[i] || old[i] == null && vnodes[i] == null) continue else if (old[i] == null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling)) else if (vnodes[i] == null) removeNodes(parent, old, i, i + 1, vnodes) else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns) diff --git a/mithril.min.js b/mithril.min.js index 4cef2674..56430b46 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -10,13 +10,13 @@ void 0,void 0,u.normalizeChildren(a),void 0,void 0):null!=a&&"object"!==typeof a void 0!==arguments[1].tag||arguments[1]instanceof Array)?d=1:(m=arguments[1],d=2);if(arguments.length===d+1)b=arguments[d]instanceof Array?arguments[d]:[arguments[d]];else for(b=[];d=r&&B>=A;){var t=c[r],w=e[A];if(t===w)r++,A++;else if(null!=t&&null!=w&&t.key===w.key)r++,A++,p(a,t,w,d,m(c,r,g),z,k),z&&t.tag===w.tag&&f(a,l(t),g);else if(t=c[v],t===w)v--,A++;else if(null!=t&&null!=w&&t.key===w.key)p(a,t,w,d,m(c,v+1,g),z,k),f(a,l(t),m(c,r,g)),v--,A++;else break}for(;v>=r&&B>=A;){t=c[v];w=e[B]; -if(t===w)v--;else if(null!=t&&null!=w&&t.key===w.key)p(a,t,w,d,m(c,v+1,g),z,k),z&&t.tag===w.tag&&f(a,l(t),g),null!=t.dom&&(g=t.dom),v--;else{if(!u){u=c;var t=v,n={},x;for(x=0;x=r&&B>=A;){var t=c[r],w=e[A];if(t===w)r++,A++;else if(null!=t&&null!=w&&t.key===w.key)r++,A++,p(a,t,w,d,m(c,r,g),z,k),z&&t.tag===w.tag&&f(a,l(t),g);else if(t=c[v],t===w)v--,A++;else if(null!=t&&null!=w&&t.key===w.key)p(a,t,w,d,m(c,v+1,g),z,k),f(a,l(t),m(c,r,g)),v--,A++;else break}for(;v>= +r&&B>=A;){t=c[v];w=e[B];if(t===w)v--;else if(null!=t&&null!=w&&t.key===w.key)p(a,t,w,d,m(c,v+1,g),z,k),z&&t.tag===w.tag&&f(a,l(t),g),null!=t.dom&&(g=t.dom),v--;else{if(!u){u=c;var t=v,n={},x;for(x=0;x