Skip to content

Commit

Permalink
reworked scroll logic, #18 #6
Browse files Browse the repository at this point in the history
  • Loading branch information
crisward committed Dec 28, 2016
1 parent d4524ea commit 06753e2
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 89 deletions.
78 changes: 45 additions & 33 deletions lib/grid2.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

riot.tag2('grid2', '<div class="gridwrap" riot-style="height:{opts.height}px"> <div class="gridbody" ref="mainbody" riot-style="left:{fixedLeft.width}px;top:{rowHeight}px;bottom:0px"> <div class="fixedLeft" riot-style="transform:translate3d({fixedLeft.left}px,{fixedLeft.top}px,0px);backface-visibility: hidden;width:{fixedLeft.width}px;bottom:1px;z-index:2;"> <gridcelltag class="cell {cell.classes()}" tag="{cell.tag}" data="{parent.opts.data}" no-reorder val="{cell.text}" cell="{cell}" each="{cell in visCells.main}" onclick="{parent.handleClick}" riot-style="position: absolute;left:{cell.left}px;top:{cell.top}px;width:{cell.width}px;height:{parent.rowHeight}px;">{cell.text}</gridcelltag> </div> </div> <div class="gridbody" ref="header" riot-style="height:{rowHeight}px;margin-right:15px"> <div class="header" riot-style="top:0px;left:0px;width:{scrollWidth}px;height:{rowHeight}px"> <div class="headercell {classes}" each="{headers.main}" no-reorder riot-style="transform:translate3d({left}px,0px,0px); backface-visibility: hidden;width:{width}px;height:{rowHeight}px;">{text}</div> </div> </div> <div class="gridbody" riot-style="width:{fixedLeft.width}px;height:{opts.height-2}px"> <div class="fixedLeft" riot-style="transform:translate3d(0px,{fixedLeft.top}px,0px);backface-visibility: hidden;width:{fixedLeft.width}px;bottom:1px;z-index:2;"> <div class="header" riot-style="top:{0 - fixedLeft.top}px;left:0px;width:{fixedLeft.width}px;height:{rowHeight}px"> <div class="headercell {classes}" each="{headers.fixed}" no-reorder riot-style="top:0px;left:{left}px;width:{width}px;height:{rowHeight}px;">{text}</div> </div> <gridcelltag class="cell {cell.classes()}" tag="{cell.tag}" no-reorder data="{parent.opts.data}" val="{cell.text}" cell="{cell}" each="{cell in visCells.fixed}" onclick="{parent.handleClick}" riot-style="position: absolute;left:{cell.left}px;top:{cell.top}px;width:{cell.width}px;height:{parent.rowHeight}px;">{cell.text}</gridcelltag> </div> </div> <div class="gridbody" ref="overlay" onscroll="{scrolling}" riot-style="overflow:auto;left:0px;top:{rowHeight}px;bottom:0px;-webkit-overflow-scrolling: touch;"> <div class="scrollArea" riot-style="background:rgba(0,0,0,0.005);width:{scrollWidth}px;height:{scrollHeight-rowHeight}px;"></div> </div> </div>', 'grid2 { display: block; -webkit-font-smoothing: antialiased; text-rendering: optimizeSpeed; } grid2 .scrollArea { transform: translateZ(0); } grid2 .gridwrap { position: relative; display: block; border: 1px solid #ccc; font-family: sans-serif; font-size: 14px; } grid2 .gridbody { position: absolute; overflow: hidden; top: 0; left: 0; right: 0; bottom: 0; transform: translateZ(0); backface-visibility: hidden; } grid2 .fixedLeft { position: absolute; top: 0; bottom: 0; } grid2 .cell, grid2 .headercell { position: absolute; box-sizing: border-box; padding: 10px 5px; whitespace: no-wrap; overflow: hidden; background: #fff; border: 1px solid #eee; border-width: 0 1px 1px 0; cursor: pointer; } grid2 .cell.active { background: #eee; } grid2 .header { position: absolute; z-index: 1; overflow: hidden; transform: translateZ(0); }', '', function(opts) {
riot.tag2('grid2', '<div class="gridwrap" riot-style="height:{opts.height}px"> <div class="gridbody" ref="mainbody" riot-style="left:{fixedLeft.width}px;top:{rowHeight}px;bottom:0px"> <div class="fixedLeft" riot-style="transform:translate3d({fixedLeft.left}px,{fixedLeft.top}px,0px);backface-visibility: hidden;width:{fixedLeft.width}px;bottom:1px;z-index:2;"> <gridcelltag class="cell {cell.classes()}" tag="{cell.tag}" data="{parent.opts.data}" no-reorder val="{cell.text}" cell="{cell}" each="{cell in visCells.main}" onclick="{parent.handleClick}" riot-style="position: absolute;left:{cell.left}px;top:{cell.top}px;width:{cell.width}px;height:{parent.rowHeight}px;">{cell.text}</gridcelltag> </div> </div> <div class="gridbody" ref="header" riot-style="height:{rowHeight}px;margin-right:15px"> <div class="header" riot-style="top:0px;left:0px;width:{scrollWidth}px;height:{rowHeight}px"> <div class="headercell {classes}" each="{headers.main}" no-reorder riot-style="transform:translate3d({left}px,0px,0px); backface-visibility: hidden;width:{width}px;height:{rowHeight}px;">{text}</div> </div> </div> <div class="gridbody" ref="fixedleft" riot-style="width:{fixedLeft.width}px;height:{opts.height-2}px"> <div class="fixedLeft" riot-style="transform:translate3d(0px,{fixedLeft.top}px,0px);backface-visibility: hidden;width:{fixedLeft.width}px;bottom:1px;z-index:2;"> <div class="header" riot-style="top:{0 - fixedLeft.top}px;left:0px;width:{fixedLeft.width}px;height:{rowHeight}px"> <div class="headercell {classes}" each="{headers.fixed}" no-reorder riot-style="top:0px;left:{left}px;width:{width}px;height:{rowHeight}px;">{text}</div> </div> <gridcelltag class="cell {cell.classes()}" tag="{cell.tag}" no-reorder data="{parent.opts.data}" val="{cell.text}" cell="{cell}" each="{cell in visCells.fixed}" onclick="{parent.handleClick}" riot-style="position: absolute;left:{cell.left}px;top:{cell.top}px;width:{cell.width}px;height:{parent.rowHeight}px;">{cell.text}</gridcelltag> </div> </div> <div class="gridbody" ref="overlay" riot-style="overflow:auto;left:{fixedLeft.width}px;top:{rowHeight}px;bottom:0px;-webkit-overflow-scrolling: touch;pointer-events:none;"> <div class="scrollArea" riot-style="background:rgba(0,0,0,0.005);width:{scrollWidth-fixedLeft.width}px;height:{scrollHeight-rowHeight}px;"></div> </div> </div>', 'grid2 { display: block; -webkit-font-smoothing: antialiased; text-rendering: optimizeSpeed; } grid2 .scrollArea { transform: translateZ(0); } grid2 .gridwrap { position: relative; display: block; border: 1px solid #ccc; font-family: sans-serif; font-size: 14px; } grid2 .gridbody { position: absolute; overflow: hidden; top: 0; left: 0; right: 0; bottom: 0; transform: translateZ(0); backface-visibility: hidden; } grid2 .fixedLeft { position: absolute; top: 0; bottom: 0; } grid2 .cell, grid2 .headercell { position: absolute; box-sizing: border-box; padding: 10px 5px; whitespace: no-wrap; overflow: hidden; background: #fff; border: 1px solid #eee; border-width: 0 1px 1px 0; cursor: pointer; } grid2 .cell.active { background: #eee; } grid2 .header { position: absolute; z-index: 1; overflow: hidden; transform: translateZ(0); }', '', function(opts) {
var calcArea, calcPos, calcVisible, reCalc, reUse;

this.on('before-mount', function() {
Expand All @@ -19,26 +19,33 @@ this.on('before-mount', function() {
this.activeCells = [];
this.activeRows = [];
this.rows = [];
return this.pushevents = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove'];
this.scrollevents = ['DOMMouseScroll', 'mousewheel', 'touchmove'];
return this.point = null;
});

this.on('mount', function() {
this.rowHeight = +opts.rowheight || 40;
this.gridbody = this.root.querySelectorAll(".gridbody");
this.pushevents.forEach((function(_this) {
return function(eventname) {
return _this.refs.overlay.addEventListener(eventname, _this.pushThroughEvent);
this.scrollevents.forEach((function(_this) {
return function(ev) {
_this.refs.mainbody.addEventListener(ev, _this.scrolling.bind(_this, true, true), false);
return _this.refs.fixedleft.addEventListener(ev, _this.scrolling.bind(_this, true, false), false);
};
})(this));
this.refs.mainbody.addEventListener('touchstart', this.scrollstart, false);
this.refs.fixedleft.addEventListener('touchstart', this.scrollstart, false);
return this.update();
});

this.on('before-unmount', function() {
return this.pushevents.forEach((function(_this) {
return function(eventname) {
return _this.refs.overlay.removeEventListener(eventname, _this.pushThroughEvent);
this.scrollevents.forEach((function(_this) {
return function(ev) {
_this.refs.mainbody.removeEventListener(ev, _this.scrolling, false);
return _this.refs.fixedleft.removeEventListener(ev, _this.scrolling, false);
};
})(this));
this.refs.mainbody.removeEventListener('touchstart', this.scrollstart, false);
return this.refs.fixedleft.removeEventListener('touchstart', this.scrollstart, false);
});

this.on('update', function() {
Expand Down Expand Up @@ -133,29 +140,6 @@ this.deselect = (function(_this) {
};
})(this);

this.pushThroughEvent = (function(_this) {
return function(e) {
var elem, error, event, top;
e.stopPropagation();
e.preventUpdate = true;
top = _this.refs.overlay.scrollTop;
try {
event = new MouseEvent(e.type, e);
} catch (error) {
event = document.createEvent('MouseEvents');
event.initMouseEvent(e.type, true, true, window, 0, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.target);
}
e.preventDefault();
_this.refs.overlay.style.display = "none";
elem = document.elementFromPoint(e.pageX, e.pageY);
if (elem != null) {
elem.dispatchEvent(event);
}
_this.refs.overlay.style.display = "block";
return _this.refs.overlay.scrollTop = top;
};
})(this);

calcPos = (function(_this) {
return function() {
var cidx, col, i, j, k, key, left, len, len1, len2, ref, ref1, ref2, ridx, row, top;
Expand Down Expand Up @@ -226,13 +210,41 @@ calcPos = (function(_this) {
})(this);

this.scrolling = (function(_this) {
return function(e) {
return function(vert, horiz, e) {
var deltaX, deltaY, point2;
e.preventUpdate = true;
e.preventDefault();
if (_this.point) {
point2 = {
x: e.changedTouches[0].clientX,
y: e.changedTouches[0].clientY
};
deltaX = _this.point.x - point2.x;
deltaY = _this.point.y - point2.y;
_this.point = point2;
} else {
deltaX = e.deltaX, deltaY = e.deltaY;
}
if (vert) {
_this.refs.overlay.scrollTop = _this.refs.overlay.scrollTop + deltaY;
}
if (horiz) {
_this.refs.overlay.scrollLeft = _this.refs.overlay.scrollLeft + deltaX;
}
_this.refs.header.scrollLeft = _this.refs.overlay.scrollLeft;
return _this.update();
};
})(this);

this.scrollstart = (function(_this) {
return function(e) {
return _this.point = {
x: e.changedTouches[0].clientX,
y: e.changedTouches[0].clientY
};
};
})(this);

calcArea = function(gridbody) {
return {
top: gridbody.scrollTop,
Expand Down Expand Up @@ -310,7 +322,7 @@ reUse = function(visible, newcells, area) {
return visible;
};
});
riot.tag2('gridcelltag', '<div><yield></yield></div>', '', '', function(opts) {
riot.tag2('gridcelltag', '<div if="{!opts.tag}"><yield></yield></div> <div if="{opts.tag}" data-is="{opts.tag}"><yield></yield></div>', '', '', function(opts) {
this.prevtag = null;

this.on('mount', function() {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"karma-spec-reporter": "0.0.20",
"mocha": "^2.0.1",
"pug": "^2.0.0-beta6",
"riot": "^3.0.2",
"riot": "^3.0.5",
"riotify": "^0.1.2",
"simulant": "^0.1.5",
"sinon": "^1.12.2",
Expand Down
60 changes: 34 additions & 26 deletions src/grid2.tag
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ grid2
.headercell(each="{headers.main}",no-reorder,riot-style="transform:translate3d({left}px,0px,0px); backface-visibility: hidden;width:{width}px;height:{rowHeight}px;",class="{classes}") {text}

//- fixed left
.gridbody(riot-style="width:{fixedLeft.width}px;height:{opts.height-2}px")
.gridbody(ref="fixedleft",riot-style="width:{fixedLeft.width}px;height:{opts.height-2}px")
.fixedLeft(riot-style="transform:translate3d(0px,{fixedLeft.top}px,0px);backface-visibility: hidden;width:{fixedLeft.width}px;bottom:1px;z-index:2;")
.header(riot-style="top:{0 - fixedLeft.top}px;left:0px;width:{fixedLeft.width}px;height:{rowHeight}px")
.headercell(each="{headers.fixed}",no-reorder,riot-style="top:0px;left:{left}px;width:{width}px;height:{rowHeight}px;",class="{classes}") {text}
gridcelltag.cell(tag="{cell.tag}",class="{cell.classes()}",no-reorder,data="{parent.opts.data}",val="{cell.text}",cell="{cell}",each="{cell in visCells.fixed}",onclick="{parent.handleClick}",riot-style="position: absolute;left:{cell.left}px;top:{cell.top}px;width:{cell.width}px;height:{parent.rowHeight}px;") {cell.text}

//- scroll area
.gridbody(ref="overlay",onscroll='{scrolling}',riot-style="overflow:auto;left:0px;top:{rowHeight}px;bottom:0px;-webkit-overflow-scrolling: touch;")
.scrollArea(riot-style="background:rgba(0,0,0,0.005);width:{scrollWidth}px;height:{scrollHeight-rowHeight}px;")
.gridbody(ref="overlay",riot-style="overflow:auto;left:{fixedLeft.width}px;top:{rowHeight}px;bottom:0px;-webkit-overflow-scrolling: touch;pointer-events:none;")
.scrollArea(riot-style="background:rgba(0,0,0,0.005);width:{scrollWidth-fixedLeft.width}px;height:{scrollHeight-rowHeight}px;")

style(type="text/stylus").
grid2
Expand Down Expand Up @@ -80,16 +80,25 @@ grid2
@activeCells = []
@activeRows = []
@rows=[]
@pushevents = ['click','dblclick','mousedown','mouseup','mousemove']
@scrollevents = ['DOMMouseScroll','mousewheel','touchmove']
@point = null

@on 'mount',->
@rowHeight = +opts.rowheight || 40
@gridbody = @root.querySelectorAll(".gridbody")
@pushevents.forEach (eventname)=> @refs.overlay.addEventListener(eventname,@pushThroughEvent)
@scrollevents.forEach (ev)=>
@refs.mainbody.addEventListener(ev,@scrolling.bind(@,true,true),false)
@refs.fixedleft.addEventListener(ev,@scrolling.bind(@,true,false),false)
@refs.mainbody.addEventListener('touchstart',@scrollstart,false)
@refs.fixedleft.addEventListener('touchstart',@scrollstart,false)
@update()

@on 'before-unmount',->
@pushevents.forEach (eventname)=> @refs.overlay.removeEventListener(eventname,@pushThroughEvent)
@scrollevents.forEach (ev)=>
@refs.mainbody.removeEventListener(ev,@scrolling,false)
@refs.fixedleft.removeEventListener(ev,@scrolling,false)
@refs.mainbody.removeEventListener('touchstart',@scrollstart,false)
@refs.fixedleft.removeEventListener('touchstart',@scrollstart,false)

@on 'update',->
return if !@gridbody || !opts.data || !opts.columns
Expand Down Expand Up @@ -133,23 +142,7 @@ grid2
@activeCells.forEach (cell)-> cell.active = false
@activeCells.length = 0
@activeRows.length = 0

@pushThroughEvent= (e)=>
e.stopPropagation()
e.preventUpdate = true
top = @refs.overlay.scrollTop #fix ie scrolling issue during click
try
event = new MouseEvent(e.type, e)
catch
event = document.createEvent('MouseEvents')
event.initMouseEvent(e.type, true,true,window,0,e.screenX,e.screenY,e.clientX,e.clientY,e.ctrlKey,e.altKey,e.shiftKey,e.metaKey,e.button,e.target)
e.preventDefault()
@refs.overlay.style.display = "none"
elem = document.elementFromPoint(e.pageX,e.pageY)
elem?.dispatchEvent(event)
@refs.overlay.style.display = "block"
@refs.overlay.scrollTop = top


calcPos= => # work out co-ordinates of all cells
left = 0
top = 0
Expand Down Expand Up @@ -194,11 +187,24 @@ grid2
@scrollHeight = top+@rowHeight
@update()

@scrolling = (e)=>
@scrolling = (vert,horiz,e)=>
e.preventUpdate = true
e.preventDefault()
if @point
point2 = {x:e.changedTouches[0].clientX,y:e.changedTouches[0].clientY}
deltaX = @point.x - point2.x
deltaY = @point.y - point2.y
@point = point2
else
{deltaX,deltaY} = e
@refs.overlay.scrollTop = @refs.overlay.scrollTop + deltaY if vert
@refs.overlay.scrollLeft = @refs.overlay.scrollLeft + deltaX if horiz
@refs.header.scrollLeft = @refs.overlay.scrollLeft
@update()

@scrollstart = (e)=>
@point = {x:e.changedTouches[0].clientX,y:e.changedTouches[0].clientY}

calcArea = (gridbody)->
top:gridbody.scrollTop
left:gridbody.scrollLeft
Expand Down Expand Up @@ -249,9 +255,11 @@ grid2


gridcelltag
div
div(if="{!opts.tag}")
<yield />

div(if="{opts.tag}",data-is="{opts.tag}")
<yield />

script(type="text/coffee").
@prevtag = null

Expand Down
30 changes: 3 additions & 27 deletions test/grid.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ describe 'grid2',->
expect(document.querySelectorAll('.headercell.identifier')[0].textContent).to.equal('#')
expect(document.querySelectorAll('.headercell.name.first')[0].textContent).to.equal('First Name')
expect(document.querySelectorAll('.headercell.name.sur')[0].textContent).to.equal('Surname')
expect(document.querySelectorAll('.cell.identifier')[0].textContent).to.equal(griddata[0].id+"")
expect(document.querySelectorAll('.cell.name.first')[0].textContent).to.equal(griddata[0].first_name)
expect(document.querySelectorAll('.cell.name.sur')[0].textContent).to.equal(griddata[0].surname)
expect(document.querySelectorAll('.cell.identifier')[0].textContent.trim()).to.equal(griddata[0].id+"")
expect(document.querySelectorAll('.cell.name.first')[0].textContent.trim()).to.equal(griddata[0].first_name)
expect(document.querySelectorAll('.cell.name.sur')[0].textContent.trim()).to.equal(griddata[0].surname)

it "should render only enough cells needed",->
expect(document.querySelectorAll('.cell').length).to.be.lt((gridheight/40)*4)
Expand All @@ -73,29 +73,6 @@ describe 'grid2',->
expect(document.querySelectorAll('.cell').length).to.be.gt((gridheight/40)*3)
done()

it "should pass events through overlay to grid below",(done)->
e = document.createEvent('MouseEvents')
# e = simulant( 'click' )
#e.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY)...
if document.createEvent
e.initMouseEvent('click', true, true, window, 1, 100, 50, 100, 50)
simulant.fire(document.querySelector('[ref=overlay]'),e)
setTimeout ->
expect(spyclick.calledOnce).to.be.true
expect(spyclick.args[0][0][0]).to.eql(griddata[0])
done()

it "should do nothing when mouse position doesn't hit an element",(done)-> # in particular it shouldn't crash
e = document.createEvent('MouseEvents')
# e = simulant( 'click' )
#e.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY)...
if document.createEvent
e.initMouseEvent('click', true, true, window, 1, -1000, 50, 1000, 50)
simulant.fire(document.querySelector('[ref=overlay]'),e)
setTimeout ->
expect(spyclick.calledOnce).to.be.false
done()

it "should change class to active when cell is clicked",(done)->
expect(@domnode.querySelectorAll('.active').length).to.equal(0)
simulant.fire(document.querySelector('.cell'),'click')
Expand All @@ -113,7 +90,6 @@ describe 'grid2',->
expect(@domnode.querySelectorAll('.active').length).to.equal(columns.length)
done()


it "should toggle a row if clicked twice with meta key",(done)->
expect(@domnode.querySelectorAll('.active').length).to.equal(0)
simulant.fire(document.querySelectorAll('.cell')[5],'click',{metaKey:true})
Expand Down
2 changes: 1 addition & 1 deletion test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<script type="riot/tag">
<progress>
<div style="border:1px solid #ccc;margin:5px;bacground:white;">
<div riot-style="height:10px;background:#4d4;width:{opts.val}%"></div>
<div riot-style="height:10px;background:#4d4;width:{opts.val}%" title="{opts.val}%"></div>
</div>
</progress>
</script>
Expand Down
2 changes: 1 addition & 1 deletion test/testtag.tag
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ testcell
div.testcell {opts.val}

testcell-obj
div.testcell-obj {opts.val.last}, {opts.val.first}
div.testcell-obj(if="{opts.val}") {opts.val.last}, {opts.val.first}

0 comments on commit 06753e2

Please sign in to comment.