I have a small function the uses a web socket to receive realtime updates. When a new response is received the function prepends a div in the html. I only want the updates to be shown in a window within the page, ie. only ~10 prepended divs should be showing at the most. Ideally I need to pop the oldest div before it overflows out of its parent div.
My question:
How do I pop divs before they overflow the parent? Considering I will receive a response nearly every second or so, what is the most efficient way of doing this?
#HTML
<div class="content">
<p>archienorman-thesis $ realtime_bitcoin</p>
<div id="messages"></div>
<!-- window content -->
</div>
#JS FUNCTION
var total = 0;
var btcs = new WebSocket('wss://ws.blockchain.info/inv');
btcs.onopen = function () {
btcs.send(JSON.stringify({"op": "unconfirmed_sub"}));
};
btcs.onmessage = function (onmsg) {
console.log(response);
var response = JSON.parse(onmsg.data);
var amount = response.x.out[0].value;
var calAmount = amount / 100000000;
var msgs = $('#messages .message');
var count = msgs.length;
if (count == 10) {
msgs.first().remove();
}
$('#messages').prepend("<p class='tx'> Amount: " + calAmount + "</p>");
}
Make the container div overflow: hidden, check if there is overflow using JS scrollHeight and clientHeight.
CSS
#messages {
overflow: hidden;
}
JS
Remove your if statement and add this after your prepend() line:
$('#messages').prepend("<p class='tx'> Amount: " + calAmount + "</p>");
$('#messages').css("overflow", "scroll");
if($('#messages')[0].scrollHeight > $('#messages').height())
msgs.last().remove();
$('#messages').css("overflow", "hidden");
The above quickly makes #messages have the overflow: scroll property in order for the scrollHeight property to work. If there is extra scroll, then it deletes the element.
See Demo.
NOTE
See my comment to your question. You should be removing last(), not first(). See the demo as an example -- try changing last() to first(), and it will not work.
I think something like this should work. This is test code that will basically remove the extra child elements when their combined width exceeds that of the container.
HTML
<div class="container">
<div>1.Test</div>
<div>2.Test</div>
<div>3.Test</div>
<div>4.Test</div>
<div>5.Test</div>
<div>6.Test</div>
<div>7.Test</div>
<div>8.Test</div>
<div>9.Test</div>
<div>10.Test</div>
<div>11.Test</div>
<div>12.Test</div>
<div>13.Test</div>
<div>14.Test</div>
<div>15.Test</div>
</div>
CSS
.container {
width:1000px;
}
.container div {
width: 100px;
display: inline-block;
}
Javascript
function a () {
var containerWidth = $('div.container').width();
var childWidth = $('div.container div').width();
var childCount = $('div.container div').length;
var removeCount = (childWidth * childCount) - containerWidth;
if(removeCount > 0) {
removeCount = Math.floor(removeCount/childWidth);
console.log(removeCount);
for(i = childCount; i > (childCount-removeCount); i--) {
$('div.container div:nth-child('+i+')').remove();
}
}
}
a();
Fiddle: https://jsfiddle.net/L3r2nk6z/5/
Related
Let's imagine I want to make a social media application. I make a div to hold all my posts. When I start the application, I only query as many requests as will fit on the viewport. I append them as divs themselves. When the user scrolls to the point that they can see n more posts, n more posts are queried and appended to the div.
Or another, an application that you can infinitely scroll and generates random numbers for each div, using similar logic as above.
How can I implement this? I have no idea where to start, but right now what I think I might be able to get away with adding a scroll event. Here's some psuedocode of how that might look, but I'm not sure if I can do something like this (or if it's valid logic, because it's late at night):
unsigned lastY
document.addEventListener('scroll', () => {
// check if there is space to add more elements
if ((lastY - postsDiv.sizeY) != 0) { // yes, there is space to add more elements
// how many can we?
unsigned toAdd =
// (I am very, very unsure if this is correct)
floor(lastY - postsDiv.sizeY) * postsDiv.lengthInYOfEachElement;
}
lastY = window.scrollY
})
Is this even a good approach?
You can use element's scrollTop property to check for amount of height scrolled. When this amount gets past a certain percentage of element's visible scroll height, you can add your posts to the element.
In the example below, new numbers are added when user scrolls 90% (0.9) of the height.
let n = 50;
let i = 0;
let cont = document.querySelector(".container");
function generateNumbers(ini) {
for (var i = ini; i <= n + ini; i++) {
let span = document.createElement("span");
span.innerText = i;
cont.appendChild(span);
}
}
generateNumbers(i);
cont.addEventListener("scroll", () => {
if (cont.scrollTop >= (cont.scrollHeight - cont.clientHeight) * 0.9) {
i = n + 1;
n += 50;
generateNumbers(i);
}
});
.container {
display: flex;
flex-direction: column;
height: 200px;
overflow: scroll;
}
<div class="container">
</div>
You can do this easily with the Intersection Observer (IO)
Basically you set up your structure like this:
let options = {
rootMargin: '0px',
threshold: 0.9
};
target = document.querySelector('#js-load-more');
observer = new IntersectionObserver(entries => {
var entry = entries[0];
if (entry.isIntersecting) {
console.log('You reached the bottom of the list!');
appendMorePosts();
}
}, options);
observer.observe(target);
appendMorePosts = function() {
const post = document.createElement('div');
post.classList.add('post');
post.innerHTML = 'blabla';
document.querySelector('.post-container').insertBefore(post,document.querySelector('#js-load-more') );
}
.post {
height: 400px;
background: linear-gradient(to bottom, hotpink, cyan)
}
<div class="post-container">
<div class="post"> blabla </div> <!-- this is one "post" with images, text, .. -->
<div class="post"> blabla </div>
<div class="post"> blabla </div>
<div id="js-load-more"></div> <!-- just an empty div, to check if you should load more -->
</div>
I want to add a class to an element when the user scrolls more than 100px from the top of the element but I seem to be triggering this as soon as the page loads. This is the code that I have at the moment
const content = document.getElementById("content");
document.addEventListener("scroll", () => {
content.classList.add(
'curtain-in',
content.scrollTop > 100
);
});
Also with your answer can you please explain where I've gone wrong.
Thank you in advance
Maybe what is happening is that content.scrollTop is always returning 0 and your condition is never fulfilled. I've struggled myself with that problem trying to make a fiddle to test your case.
To check if the scroll has passed the beginning of the element plus 100px we need to know where the element starts and the new position of the scroll, we can get both values like this:
var position = content.offsetTop;
var scrolled = document.scrollingElement.scrollTop;
With these, you can do something like this in your event function:
const content = document.getElementById("content");
document.addEventListener("scroll", (e) => {
var scrolled = document.scrollingElement.scrollTop;
var position = content.offsetTop;
if(scrolled > position + 100){
content.classList.add(
'curtain-in');
}
});
Here's the fiddle: https://jsfiddle.net/cvmw3L1o/1/
I want to add a class to an element when the user scrolls more than
100px from the top of the element
You should add addEventListener to content not document
const content = document.getElementById("content");
content.addEventListener("scroll", () => {
console.log('class added');
content.classList.add(
'curtain-in',
content.scrollTop >= 100
);
});
#content {
height: 200px;
width: 200px;
overflow: scroll;
}
p {
height: 1000px;
}
<div id="content">
<p></p>
</div>
I have a div on which I have a directive that binds HTML content and compile it (sort of ng-bing-html directive, but that also compile html to allow insertion of custom directives). The HTML code looks like this :
<div ng-repeat="text in texts">
<div class="content-display"
bind-html-compile="text | filterThatOutputsHTMLCodeWithCustomDirectives | nl2br">
</div>
</div>
The problem is I need to display only a restricted portion of each of the content-display divs, and have a "read more..." button that would expand the corresponding div to its full size. But I CANNOT truncate the text bound in the div, since it's not only text, but can contain HTML tags/directives.
I found this JQuery code, that accomplish what I want visually : https://stackoverflow.com/a/7590517/2459955 (JSFiddle here : http://jsfiddle.net/rlemon/g8c8A/6/ )
The problem is that it's not Angular-compliant, and is pure JQuery. And since my div in which I bind the HTML content is inside an ng-repeat... this solution wouldn't work when the texts array gets refreshed asynchronously.
Do you see a way to have the same behavior as in the answer linked earlier, but being more "Angular compliant" and applying it automatically to each of the content-display divs added by the ng-repeat ?
Consider using a CSS approach like the one described here: https://css-tricks.com/text-fade-read-more/
CSS:
.sidebar-box {
max-height: 120px;
position: relative;
overflow: hidden;
}
.sidebar-box .read-more {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
margin: 0; padding: 30px 0;
/* "transparent" only works here because == rgba(0,0,0,0) */
background-image: linear-gradient(to bottom, transparent, black);
}
Rather than use jQuery for the read more "reveal", you could create an AngularJS directive for the read more button.
Directive (untested):
angular.module('myApp')
.directive('readMore', readMoreDirective);
function readMoreDirective() {
return function(scope, iElement) {
scope.$on('click', function() {
var totalHeight = 0;
var parentElement = iElement.parent();
var grandparentElement = parentElement.parent();
var parentSiblings = grandparentElement.find("p:not('.read-more')");
// measure how tall inside should be by adding together heights
// of all inside paragraphs (except read-more paragraph)
angular.forEach(parentSiblings, function(ps) {
totalHeight += ps.outerHeight();
});
grandparentElement.css({
// Set height to prevent instant jumpdown when max height is removed
height: grandparentElement.height(),
'max-height': 9999
})
.animate({
height: totalHeight
});
});
};
}
One clean way would be using a class for truncated div, and remove it to display all the text :
Angular scope :
$scope.truncated = []; // make new array containing the state of the div (truncated or not)
for(var i; i < texts.length -1; i++){
$scope.truncated.push(0); // fill it with 0 (false by default)
}
$scope.textTruncate = function(index) {
$scope.truncated[index] = !$scope.truncated[index]; // toggle this value
}
Angular view :
<div ng-repeat="text in texts" ng-class="{truncated: truncated[$index]}">
<div class="content-display"
bind-html-compile="text | filterThatOutputsHTMLCodeWithCustomDirectives | nl2br">
</div>
<button ng-click="textTruncate($index)" >Read more</button>
</div>
CSS :
.content-display {
max-height: 1000px; /* should be your max text height */
overflow: hidden;
transition: max-height .3s ease;
}
.truncated .content-display {
max-height: 100px; /* or whatever max height you need */
}
That is what comes in my mind, not sure if it's the most efficient way.
Try using <p data-dd-collapse-text="100">{{veryLongText}}</p> inside the ng-repeat
Documentation Here
Finally, I ended up using the approach given in this answer with a slight modification : https://stackoverflow.com/a/7590517/2459955
Indeed, since I have a ng-repeat adding more divs into the DOM, the $elem.each() function wouldn't trigger for these additional divs. The solution is to use a JQuery plugin called jquery.initialize.
This plugin gives an $elem.initialize() function that has exactly the same syntax as $elem.each() but initialize() will call the callback again on new items matching the provided selector automatically when they will be added to the DOM. It uses MutationObserver.
The final code looks like this. I have some JQuery code in my module.run() entry (run once at module initialization):
var slideHeight = 400;
$(".content-collapse").initialize(function() {
var $this = $(this);
var $wrap = $this.children(".content-display");
var defHeight = $wrap.height();
if (defHeight >= slideHeight) {
var $readMore = $this.find(".read-more");
var $gradientContainer = $this.find(".gradient-container");
$gradientContainer.append('<div class="gradient"></div>');
$wrap.css("height", slideHeight + "px");
$readMore.append("<a href='#'>Read more</a>");
$readMore.children("a").bind("click", function(event) {
var curHeight = $wrap.height();
if (curHeight == slideHeight) {
$wrap.animate({
height: defHeight
}, "normal");
$(this).text("Read less");
$gradientContainer.children(".gradient").fadeOut();
} else {
$wrap.animate({
height: slideHeight
}, "normal");
$(this).text("Read more");
$gradientContainer.children(".gradient").fadeIn();
}
return false;
});
}
});
And the corresponding HTML (cleaned for demonstration purpose):
<div class="content-collapse" ng-repeat="text in texts">
<div class="content-display" bind-html-compile="::text"></div>
<div class="gradient-container"></div>
<div class="read-more"></div>
</div>
This solution allows for smooth expand/collapse animation that works fine without any CSS hack, it adds the "Read more" button only on answers that exceeds the desired size limit, and works even if the texts array is modified by asynchronous requests.
I had a similar issue. I had o implement this for a data table. I found following directive and it worked smoothly as per requirements:-
Ui Framework- Angular js
In Html
<tr data-ng-repeat="proj in errors">
<td dd-text-collapse dd-text-collapse-max-length="40"
dd-text-collapse-text="{{proj.description}}"></td>
in Javascript:-
app.directive('ddTextCollapse', ['$compile', function($compile) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
/* start collapsed */
scope.collapsed = false;
/* create the function to toggle the collapse */
scope.toggle = function() {
scope.collapsed = !scope.collapsed;
};
/* wait for changes on the text */
attrs.$observe('ddTextCollapseText', function(text) {
/* get the length from the attributes */
var maxLength = scope.$eval(attrs.ddTextCollapseMaxLength);
if (text.length > maxLength) {
/* split the text in two parts, the first always showing */
var firstPart = String(text).substring(0, maxLength);
var secondPart = String(text).substring(maxLength, text.length);
/* create some new html elements to hold the separate info */
var firstSpan = $compile('<span>' + firstPart + '</span>')(scope);
var secondSpan = $compile('<span ng-if="collapsed">' + secondPart + '</span>')(scope);
var moreIndicatorSpan = $compile('<a ng-if="!collapsed">... </a>')(scope);
var lineBreak = $compile('<br ng-if="collapsed">')(scope);
var toggleButton = $compile('<a class="collapse-text-toggle" ng-click="toggle()">{{collapsed ? "(less)" : "(more)"}}</a>')(scope);
/* remove the current contents of the element
and add the new ones we created */
element.empty();
element.append(firstSpan);
element.append(secondSpan);
element.append(moreIndicatorSpan);
element.append(lineBreak);
element.append(toggleButton);
}
else {
element.empty();
element.append(text);
}
});
}
};
}]);
I want an image inside div to be moving vertically down whenever a user scrolls. It has to end only at the end of the page. Have used the below script solution from the another post, however it doesn`t help.
HTML
<div id="wrapper">
<img src="images/samp_scroll.png" class="ïmage">
</div>
CSS:
#wrapper {
position: absolute;
}
.image {
width: 560px;
height: 700px;
}
Script:
window.onscroll = function (e) {
var vertical_position = 0;
if (pageYOffset)//usual
vertical_position = pageYOffset;
else if (document.documentElement.clientHeight)//ie
vertical_position = document.documentElement.scrollTop;
else if (document.body)//ie quirks
vertical_position = document.body.scrollTop;
console.log( vertical_position);
var your_div = document.getElementById('wrapper');
your_div.top = (vertical_position + 200) + 'px';//200 is arbitrary.. just to show you could now position it how you want
}
What is that I am doing wrong here?? Verfying this further, though I could get the console value vertical_position I can`t really move the div using the below code.
var your_div = document.getElementById("wrapper");
your_div.top = 400 + 'px';
Is there any other best way to deal with Moving html elements with DIV? Any articles which explains? I am really new to this. Please help me out.
Adding style before the top, worked. However looks like the style applies only once as it moves only once as I scroll. Any ideas.
One small miss, it should be style.top:
your_div.style.top = (vertical_position + 200) + 'px';
Replace
var your_div = document.getElementById('wrapper');
your_div.top = (vertical_position + 200) + 'px';//200 is arbitrary.. just to show you could now position it how you want
with
$("#wrapper").css("top",(vertical_position + 200) + 'px');
I have a div section on my .ASPX form. The section just contains a load of links (standard
document.getElementById('Side1').style.display = 'none';
This worked great but was a bit abrupt for what I wanted, so I wrote the little routine below (with a little help from the internet) but although the DIV dection shrinks, and the content below scrolls up .. the links in the div section don't move, until the div section is made invisible .... is there a way round this, or am i going about this all wrong (ps my javascript is rubbish)
var originalSize =0;
var i = 0;
var ts;
function shrink() {
if (i != 28) {
document.getElementById('Side1').style.height = parseInt(document.getElementById('Side1').style.height) - 5 + 'px';
i++;
ts = setTimeout("shrink()", 10);
}
else {
document.getElementById('Side1').style.display = 'none';
i = 0;
clearTimeout(ts);
}
}
You probably just need to add this to your CSS:
#Side1 { overflow: hidden; }