{"action":"created","issue":{"url":"https://api.github.com/repos/lhorie/mithril.js/issues/813","labels_url":"https://api.github.com/repos/lhorie/mithril.js/issues/813/labels{/name}","comments_url":"https://api.github.com/repos/lhorie/mithril.js/issues/813/comments","events_url":"https://api.github.com/repos/lhorie/mithril.js/issues/813/events","html_url":"https://github.com/lhorie/mithril.js/issues/813","id":111544522,"number":813,"title":"Diff missing changes on element replacement","user":{"login":"AugustBrenner","id":4537070,"avatar_url":"https://avatars.githubusercontent.com/u/4537070?v=3","gravatar_id":"","url":"https://api.github.com/users/AugustBrenner","html_url":"https://github.com/AugustBrenner","followers_url":"https://api.github.com/users/AugustBrenner/followers","following_url":"https://api.github.com/users/AugustBrenner/following{/other_user}","gists_url":"https://api.github.com/users/AugustBrenner/gists{/gist_id}","starred_url":"https://api.github.com/users/AugustBrenner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AugustBrenner/subscriptions","organizations_url":"https://api.github.com/users/AugustBrenner/orgs","repos_url":"https://api.github.com/users/AugustBrenner/repos","events_url":"https://api.github.com/users/AugustBrenner/events{/privacy}","received_events_url":"https://api.github.com/users/AugustBrenner/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2015-10-15T04:48:38Z","updated_at":"2015-10-15T08:29:07Z","closed_at":null,"body":"If there is a scenario where an array of elements with different style or different text, and the element from the beginning is shifted off and pushed onto the end, the diff will not always recognize the change properly and even though many of the elements attributes and attached components will update properly, the element is sometimes left with the same text or color as before.\r\n\r\nIn the following example I have a text post component on top and a comment component on the bottom, both are feeding from the same data source\r\n\r\n![screen shot 2015-10-14 at 9 41 05 pm](https://cloud.githubusercontent.com/assets/4537070/10504966/9ad745d2-72bc-11e5-8dcc-f73777aed427.png)\r\n\r\n\r\nAs I scroll the page up the data source array will shift the component off the array as soon as it passes 50px outside of the screen\r\n\r\n\r\n\r\n![screen shot 2015-10-14 at 9 41 36 pm](https://cloud.githubusercontent.com/assets/4537070/10504971/a25c9730-72bc-11e5-8a29-2c68e78f9dd3.png)\r\n\r\n\r\n\r\nNearly There this is the moment of truth..........................................................................\r\n\r\n\r\n\r\n\r\n![screen shot 2015-10-14 at 9 42 06 pm](https://cloud.githubusercontent.com/assets/4537070/10504972/a691eb34-72bc-11e5-80a3-d45ee7fe4b36.png)\r\n\r\nAnd there you go, The item was shifted off the array. The virtual DOM diffing did it's work to remove the comment elements from the DOM because they clearly changed, as the first Item it the array no longer has any comments associated with it. But as you can see the text (which is wrapped inside an <a> tag) did not update.  Mithril did not sense that the text values had changed, and reused the same elements to reconstruct the DOM.\r\n\r\n\r\n![screen shot 2015-10-14 at 9 42 40 pm](https://cloud.githubusercontent.com/assets/4537070/10504997/05c0569a-72bd-11e5-881a-a12da91919c6.png)\r\n\r\n\r\n\r\n\r\nI have also been able to recreate this issue setting the background-color style.\r\nHere is a demo I made from your getting started that has the issue in case you wanted to debug it\r\n\r\n\r\n`<!doctype html>`\r\n`<script src=\"mithril.min.js\"></script>`\r\n`<script>`\r\n`    var InfiniteScroll = function(element, func, options){`\r\n\r\n`        options = options || {}`\r\n\r\n`        var disabled = false;`\r\n\r\n`        var container = options.container || window`\r\n\r\n`        this.disable = function(disable){`\r\n`            disabled = disable;`\r\n`        }`\r\n\r\n`        this.destroy = function(){`\r\n`            container.removeEventListener('scroll', handleScroll)`\r\n`            container.removeEventListener('resize', handleScroll)`\r\n`        }`\r\n\r\n`        container.addEventListener('scroll', handleScroll)`\r\n`        container.addEventListener('resize', handleScroll)`\r\n\r\n\r\n`        function handleScroll(){`\r\n\r\n`            if(disabled) return`\r\n\r\n`            colors.vm.isScrolling = true    `\r\n\r\n`            var rect = element.getBoundingClientRect()`\r\n\r\n`            if(rect.bottom <= window.innerHeight){`\r\n`                console.log('run')`\r\n`                func()`\r\n`            }`\r\n\r\n`            colors.vm.viewPort.top = ~rect.top + 1`\r\n`            colors.vm.viewPort.bottom = colors.vm.viewPort.top + window.innerHeight`\r\n\r\n`            console.log(colors.vm.viewPort)`\r\n`            m.redraw()`\r\n\r\n`            return`\r\n\r\n`        }`\r\n`    }`\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n`var MIN_WIDTH = 250`\r\n\r\n\r\n`//this application only has one component: colors`\r\n`var colors = {};`\r\n\r\n`//for simplicity, we use this component to namespace the model classes`\r\n\r\n`//the Colors class has two properties`\r\n`colors.Colors = function(data) {`\r\n`    this.initialHeight = m.prop(data.height);`\r\n`    this.color = m.prop(data.color);`\r\n`    this.height = m.prop(0);`\r\n`    this.top = m.prop(0);`\r\n`    this.left = m.prop(0);`\r\n`    this.shouldDisplay = m.prop(true);`\r\n`};`\r\n\r\n`//the ColorsList class is a list of Colors's`\r\n`colors.ColorsList = Array;`\r\n\r\n`//the view-model tracks a running list of colors,`\r\n`//stores a description for new colors before they are created`\r\n`//and takes care of the logic surrounding when adding is permitted`\r\n`//and clearing the input after adding a colors to the list`\r\n`colors.vm = (function() {`\r\n`    var vm = {}`\r\n`    vm.init = function() {`\r\n`        //a running list of colors`\r\n`        vm.list = new colors.ColorsList();`\r\n\r\n`        // height`\r\n`        vm.getHeight = function(){`\r\n`            return Math.floor(Math.random() * 400) + 200 + 'px'`\r\n`        }`\r\n\r\n`        vm.getColor = function(){`\r\n`            return '#'+Math.floor(Math.random()*16777215).toString(16);`\r\n`        }`\r\n\r\n`        vm.container = m.prop(\"\")`\r\n\r\n`        vm.setContainer = function(element, isInitialized){`\r\n`            if(isInitialized) return`\r\n\r\n`            vm.container(element)`\r\n\r\n`            // setup infinite scroll`\r\n`            var infiniteScroll = new InfiniteScroll(vm.container(), vm.add);`\r\n\r\n`        }`\r\n\r\n`        vm.getWidth = function(){`\r\n`            return (1 / Math.floor(vm.container().clientWidth / MIN_WIDTH)) * 100 + '%'`\r\n`        }`\r\n\r\n`        vm.getColumnMin = function(index){`\r\n`            var index = 0`\r\n`            var length = vm.columns[0]`\r\n`            var i = vm.columns.length`\r\n`            while(i--){`\r\n`                if(vm.columns[i] < length){`\r\n`                    index = i;`\r\n`                    length = vm.columns[i]`\r\n`                }`\r\n`            }`\r\n`            return index`\r\n`            // var columnLength = Math.floor(vm.container().clientWidth / MIN_WIDTH)`\r\n`            // return (index % columnLength)`\r\n`        }`\r\n\r\n`        vm.viewPort = {`\r\n`            top:0,`\r\n`            bottom:0`\r\n`        }`\r\n\r\n\r\n`        vm.columns = []`\r\n`        vm.setColumns = function(){`\r\n`            colors.vm.columns = []`\r\n`            for(var i = 0, len = Math.floor(vm.container().clientWidth / MIN_WIDTH); i < len; i++){`\r\n`                vm.columns.push(0)`\r\n`            }`\r\n`        }`\r\n\r\n`        vm.getLongestColumn = function(){`\r\n`            var index = 0`\r\n`            var length = 0`\r\n`            var i = vm.columns.length`\r\n`            while(i--){`\r\n`                if(vm.columns[i] > length){`\r\n`                    index = i;`\r\n`                    length = vm.columns[i]`\r\n`                }`\r\n`            }`\r\n`            return length`\r\n`        }`\r\n\r\n`         vm.getShortestColumn = function(){`\r\n`            var index = 0`\r\n`            var length = vm.columns[0]`\r\n`            var i = vm.columns.length`\r\n`            while(i--){`\r\n`                if(vm.columns[i] < length){`\r\n`                    index = i;`\r\n`                    length = vm.columns[i]`\r\n`                }`\r\n`            }`\r\n`            return index`\r\n`        }`\r\n\r\n`        vm.startTime = 0;`\r\n\r\n`        vm.elementsToRender = 0`\r\n\r\n`        vm.isScrolling = true`\r\n\r\n`        vm.setPosition = function(element, isInitialized, context, vdom){`\r\n\r\n`            if(vm.isScrolling === true) element.style['transition'] = 'none'`\r\n\r\n`            var index = vdom.attrs.index;`\r\n\r\n`            var height = element.clientHeight`\r\n\r\n`            var column = vm.getColumnMin(index)`\r\n\r\n`            if(vm.list[index].height() === 0){`\r\n\r\n`                element.style['top'] = vm.columns[column] + 'px'`\r\n\r\n`                vm.columns[column] += height`\r\n\r\n`                element.style['left'] = (1 / Math.floor(vm.container().clientWidth / MIN_WIDTH)) * 100 * column + '%'`\r\n`            }`\r\n\r\n`            vm.list[index].height(height)`\r\n\r\n`            if(vm.getLongestColumn() > vm.container().clientHeight){`\r\n`                vm.container().style.height = vm.getLongestColumn() + 'px'`\r\n`            }`\r\n\r\n`            element.style['transition'] = 'top 0.15s linear, left 0.15s linear'`\r\n\r\n                \r\n`            vm.elementsToRender--`\r\n\r\n`            if(vm.elementsToRender === 0){`\r\n`                vm.isScrolling = false`\r\n`            }`\r\n\r\n`            if(vdom.attrs.index === vm.list.length - 1){`\r\n`                console.log(Date.now() - vm.startTime)`\r\n`            }`\r\n`        }`\r\n\r\n`        //adds a colors to the list, and clears the description field for user convenience`\r\n`        vm.add = function() {`\r\n`            var i = 40`\r\n`            while(i--){`\r\n`                vm.list.push(new colors.Colors({height: vm.getHeight(), color: vm.getColor()}));`\r\n`            }`\r\n`            m.redraw()`\r\n`        };`\r\n\r\n`        vm.changeHeight = function(index){`\r\n`            return function(){`\r\n`                vm.list[index].initialHeight(vm.getHeight())`\r\n`            }`\r\n`        }`\r\n\r\n\r\n`    }`\r\n`    return vm`\r\n`}())`\r\n\r\n`//the controller defines what part of the model is relevant for the current page`\r\n`//in our case, there's only one view-model that handles everything`\r\n`colors.controller = function() {`\r\n`        colors.vm.init()`\r\n`}`\r\n\r\n`//here's the view`\r\n`colors.view = function() {`\r\n`    colors.vm.startTime = Date.now()`\r\n`    return m(\"html\", [`\r\n`        m(\"body\", [`\r\n`            m(\"div\", {config: colors.vm.setContainer, style: {'position': 'relative', 'width':'100%', 'height':'20px'}}, [`\r\n\r\n`                function(){`\r\n\r\n`                    var elements = []`\r\n`                    colors.vm.setColumns()`\r\n`                    console.log('Column Reset')`\r\n\r\n`                    var reversionColumns = []`\r\n`                    for(var i in colors.vm.columns){`\r\n`                        reversionColumns[i] = 0`\r\n`                    }`\r\n\r\n`                    var len = colors.vm.list.length`\r\n`                    var head = colors.vm.list.length - 1`\r\n`                    while(len--){`\r\n`                        var index = head - len;`\r\n\r\n`                        var post = colors.vm.list[index]`\r\n                            \r\n`                        var height = post.height()`\r\n\r\n`                        if(height > 0){`\r\n\r\n`                            var column = colors.vm.getColumnMin(index)`\r\n`                            var columnHeight = colors.vm.columns[column]`\r\n\r\n\r\n`                            if(columnHeight > colors.vm.viewPort.bottom){`\r\n`                                // console.log('Past Viewport: ' + index)`\r\n`                                break`\r\n`                            }`\r\n\r\n`                            post.top(columnHeight)`\r\n`                            colors.vm.columns[column] += height`\r\n\r\n\r\n`                            if((columnHeight + height) < colors.vm.viewPort.top){`\r\n`                                // console.log('Before Viewport: ' + index)`\r\n`                                continue`\r\n`                            }`\r\n`                            else{`\r\n`                                reversionColumns[column] += height`\r\n`                                post.height(0)`\r\n`                            }`\r\n\r\n`                            post.left((1 / Math.floor(colors.vm.container().clientWidth / MIN_WIDTH)) * 100 * column)`\r\n`                        }`\r\n\r\n`                        elements.push(m(\"div\", {onclick: colors.vm.changeHeight(index), config: colors.vm.setPosition, index: index, style: {'position':'absolute', 'height': post.initialHeight(), 'top': post.top() + 'px', 'left': post.left() + '%', 'width': colors.vm.getWidth(), 'background-color': post.color()}}, index + \"    Hey Leo, you're lookin' real cool today\"))`\r\n`                    }`\r\n\r\n`                    for(var i in reversionColumns){`\r\n`                        colors.vm.columns[i] -= reversionColumns[i]`\r\n`                    }`\r\n\r\n`                    colors.vm.elementsToRender = elements.length`\r\n\r\n`                    return elements`\r\n\r\n`                }(),`\r\n\r\n`                m(\"button\", {onclick: colors.vm.add, style:{'position':'absolute', 'bottom': '0', 'left':'10px'}}, \"Add\")`\r\n`            ])`\r\n`        ])`\r\n`    ])`\r\n`};`\r\n\r\n\r\n`m.mount(document, {controller: colors.controller, view: colors.view});`\r\n\r\n`</script>`"},"comment":{"url":"https://api.github.com/repos/lhorie/mithril.js/issues/comments/148316626","html_url":"https://github.com/lhorie/mithril.js/issues/813#issuecomment-148316626","issue_url":"https://api.github.com/repos/lhorie/mithril.js/issues/813","id":148316626,"user":{"login":"AugustBrenner","id":4537070,"avatar_url":"https://avatars.githubusercontent.com/u/4537070?v=3","gravatar_id":"","url":"https://api.github.com/users/AugustBrenner","html_url":"https://github.com/AugustBrenner","followers_url":"https://api.github.com/users/AugustBrenner/followers","following_url":"https://api.github.com/users/AugustBrenner/following{/other_user}","gists_url":"https://api.github.com/users/AugustBrenner/gists{/gist_id}","starred_url":"https://api.github.com/users/AugustBrenner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AugustBrenner/subscriptions","organizations_url":"https://api.github.com/users/AugustBrenner/orgs","repos_url":"https://api.github.com/users/AugustBrenner/repos","events_url":"https://api.github.com/users/AugustBrenner/events{/privacy}","received_events_url":"https://api.github.com/users/AugustBrenner/received_events","type":"User","site_admin":false},"created_at":"2015-10-15T08:29:07Z","updated_at":"2015-10-15T08:29:07Z","body":"You were exactly right.  I added a key attribute and it fixed all of the issues. Not a bug after all. Thank you!"}}