How can I make an virtual scroll with angularJS? - javascript

I'm trying to make a directive that I can do a virtual scroll, so as the user scrolls the table, the table remove "old" views and add "new" views, kind like of collection repeat but I've been failing, I think I didn't understand the math behind it, can someone help me?
this is my directive code:
BaseModule.directive('myScroll', function() {
return {
restrict:"A",
scope:{
rows:"=",
headers:"="
},
link: function(scope,el) {
var scrollTop = 0;
var scrollLeft = 0;
angular.element(el).on('scroll',function(){
scrollTop = $(el).scrollTop();
scrollLeft = $(el).scrollLeft();
$(el).parent().find(".header").scrollLeft(scrollLeft);
var height = $(el).height();
var numberOfRows = height/23;
var initialRow = scrollTop/23;
var html = "";
for(i=0; i<numberOfRows;i++){
var row = scope.rows[i+initialRow];
html = html + addRow(row,i+initialRow);
}
$(el).find('.tbody-scroll').append(html);
});
scope.$watch('rows',function(rows){
var height = $(el).height();
var numberOfRows = height/23;
var initialRow = scrollTop/23;
var html = "";
for(i=0; i<numberOfRows;i++){
var row = rows[i+initialRow];
html = html + addRow(row,i+initialRow);
}
$(el).find('.tbody-scroll').append(html);
});
var addRow = function(row,index){
var html = "";
var pos = 0;
var totalWidth = 0;
angular.forEach(row,function(col){
var width = scope.headers[pos].width;
totalWidth = totalWidth + width;
html = html + "<span style='width:"+width+"px'>"+col.value+"</span>";
pos++;
});
html = "<div class='row' style='top:"+index*23+"px;width:"+totalWidth+"px;'>"+html;
html = html + "</div>";
return html;
};
}
};
});
<!-- my directive .html -->
<div class="mTable">
<div class="header" ng-style="headerWidth(headers())">
<span ng-repeat="header in headers()" ng-style="widthStyle(header)">
{{::header.label}}
</span>
</div>
<div class="tbody-container" my-scroll headers="headers()" rows="rows()">
<div class="tbody-scroll" ng-style="scrollHeight(rows(),headers())"></div>
</div>
</div>

Giving a full answer with code might require a bit too much of an effort
This library implements virtual scroll on ng-repeat https://github.com/stackfull/angular-virtual-scroll in the description there's also an article on how to implement this feature on your own.
The basic concept is to make two divs, one above and one below the list, which size is determined by the number of elements inside the list (this approach has a limitation since list elements must either have all the same height or their height must be fixed), then you delete elements as they disappear from the viewport and resize the divs according to the number of elements not currently rendered and your position on the list

Not a direct answer to your question, but an alternative: You may want to look at the ui-scroll directive which is a replacement for ng-repeat and has a similar function.
Example
in your controller
$scope.movieDataSource = {
get : function (index, count, callback) {
var i, items = [$scope.userMovies], item;
var min = 1;
var max = 1000;
for (i=index ; i<index + count ; i++) {
if(i < min || i > max) {
continue;
}
item = {
title: $scope.userMovies.title,
imageURL: $scope.userMovies.poster_path
};
items.push (item);
}
callback (items);
}
}
And in your view:
<div ui-scroll="item in movieDataSource">
{{item.title}}
</div>

Related

How to dynamically create multiple horizontal bar charts in Google Chart?

I'm new in Google Chart API.
I want to use it to visualize my data about the review on Google Play Store which include multiple issues, sentiments and other conditions.
I want to build a horizontal bar chart which x axis containing different app , and each app's containing 7 issues, y axis is the sum of sentiment of each issue in different app.
I have already done the horizontal chart containing all data in a single div element. However, for the user's convenience, I want to show 5 data at most in a single div element, and dynamically create div element if there is more than 5 data in the current data set. At last, the div elements which paint the chart will horizontally append to the another div element called issueBar_div. The user can use the scrollbar to view different chart.
What I've done:
Partition data:
var title = ['APP'];
var issueRow = {{ projectissues|safe }}; // 7 different issues got from Django
var graph_data = [title.concat(issueRow)];
var cnt = 0;
var divide_row = [];
var tableRows = ... // tableRows is the app name mapping to sentiment sum which corresponding to different issue array dictionary.
// like the form APP -> [20, 12, -1, 3, 5, 21, 57]
for (var app in tableRows) {
cnt ++;
var row = [app];
for (var i = 0; i < tableRows[app].length; i++) {
row.push(tableRows[app][i]);
}
if(cnt < 6){
divide_row.push(row);
}
else{
graph_data.push(divide_row);
divide_row = [];
cnt = 0;
}
}
Create the div element and draw the chart
In order to build a use the scrollbar, I add some restriction to the issueBar_div.
<div id="issueBar_div" style="height:400px; overflow-x:scroll; overflow-y:hidden";></div>
Dynamically create the div element and draw.
function drawIssueBar() {
var issueBar = document.getElementById("issueBar_div");
// titleRow include the APP and other issue
var titleRow = graph_data[0];
delete_element();
for(var i = 1;i<graph_data.length;i++){
// create div element and set their attribute
var my_div = document.createElement("div");
my_div.id = "issuebar_" + i;
my_div.style.display = "table-cell";
// append this element to #issueBar_div
issueBar.appendChild(my_div);
// get the sliced data and push to total_data
var row_data = graph_data[i];
var total_data = [titleRow];
for(var k=0;k<row_data.length;k++){
total_data.push(row_data[k]);
}
// the new data container
var data = new google.visualization.arrayToDataTable(total_data);
var div_width = $("#home").width();
var materialOptions = {
height: 400,
width: div_width,
hAxis: {
title: 'Sum of sentiment'
},
vAxis: {
title: 'APP'
},
bars: 'vertical'
};
var materialChart = new google.charts.Bar(document.getElementById("issuebar_" + i));
materialChart.draw(data, materialOptions);
}
}
// delete the div element
function delete_element(){
i = 1;
while(true){
element = document.getElementById("issuebar_" + i);
if(element !== null){
element.outerHTML = "";
delete element;
i++;
}
else{
break;
}
}
}
Current Problem:
Basically, the created div elements will create as expect, but the chart will only show one chart as below.
The successful chart
The fail part when move the scrollbar to right
How can I solve this problem?
Any answer will be greatly appreciated. Thanks.
ALL. I have already solve my problem!!!
I notice that there is a very important sentence showing on Google Chart document:
For each chart on the page, add a call to google.charts.setOnLoadCallback() with the callback that draws the chart as an input
So I decide to callback drawing method when drawing each bar chart.
The specific solution
1.I use the new Function as a callback function. The row_data is the data needing to present, and the my_div is the div element where to store the chart.
var drawIssueBar = new Function("row_data", "my_div",
"var data = google.visualization.arrayToDataTable(row_data);" +
"var div_width = $('#home').width();" +
"var materialOptions = {" +
"height: 400," +
"width: div_width," +
"hAxis: {" +
"title: 'Sum of sentiment'" +
"}," +
"vAxis: {" +
"title: 'APP'" +
"}," +
"bars: 'vertical'" +
"};"+
"var materialChart = new google.charts.Bar(my_div);" +
"materialChart.draw(data, materialOptions);"
);
2.Rewrite the data processing.
var titleRow = ... // like the form of ["APP", "issue1", "issue2", ..."issue7"]
var cnt = 0;
var divide_row = [];
for (var app in tableRows) {
cnt ++;
var row = [app].concat(tableRows[app]);
if(cnt < 6){
divide_row.push(row);
}
else{
graph_data.push([titleRow].concat(divide_row));
// console.log(graph_data[graph_data.length - 1]);
divide_row = [];
cnt = 0;
}
}
3.Write a new function called drawIssueChart to draw all the bar chart.
function drawIssueChart(){
// create the div element and draw the chart
delete_element();
var issueBar = document.getElementById('issueBar_div');
for(var i = 0;i<graph_data.length;i++){
var my_div = document.createElement("div");
my_div.id = "issuebar_" + i;
my_div.style.display = "table-cell";
issueBar.appendChild(my_div);
var row_data = graph_data[i];
// use the drawIssueBar as a callback
google.charts.setOnLoadCallback(drawIssueBar(row_data, my_div));
}
}
Result
The successful chart
Another successful chart when moving the scrollbar to right
I learn a lot from this solution!
Hopefully it can help the people who encounter this problem.

jQuery dynamically add images to table

I need to add images to a given table. I have the following code:
HTML:
<div class="container" id="game"></div>
Javascript
function table() {
var i,
x,
domRow,
domCol,
rows = $("#rows").val(),
colums = $("#columns").val(),
table = $('<table>'),
cellId = 0;
table.empty();
for(i = 0; i < rows; i++) {
domRow = $('<tr/>');
for(x = 0; x < colums; x++) {
domCol = $('<td/>',{
'id': "cell-" + cellId++,
'class': "cell",
'text': 'cell',
'data-row': i,
'data-col': x
});
domRow.append(domCol);
}
table.append(domRow);
}
return table;
}
Now I want do add images to each data cell from another function.
Example:
function images() {
var game = $("game");
// TODO the images need to be added too
game.append(table())
}
An image with the name 0.png needs to be added to the data cell with the id="cell-0" and so on... (1.png to id="cell-1")
How could I do this?
The jQuery append method can take a function that returns the HTML string to append. And within that function this refers to the element. So you can just find all the td elements in your table and append the right image to each one:
function images() {
var game = $("game");
var tableEl = table();
tableEl.find('td').append(function () {
// `this` is the <td> element jQuery is currently appending to
var num = this.id.split('-')[1];
return '<img src="' + num + '.png" />';
});
game.append(tableEl)
}
Try setting window.myTable or similar to the output of table(), and then edit the table by acessing it from window.myTable.
For adding the images, what I would instead recommend is just inserting:
var img = $('<img>');
img.attr('src', parseInt(cellId) + ".png");
img.appendTo(domCol);
Right before domRow.append(domCol); (I did not test this).
Here is a simple code to add your images in each cell that its id correspond.
$('[id^=cell-]').each(function() {
var curCell = $(this);
curCell.html('<img src="' + curCell.attr('id').substring(5) + '.png">');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<table>
<tr><td id="cell-0">1</td><td id="cell-1">2</td></tr>
</table>

Hiding and showing classes with the same Class with jQuery

I'm creating a tool which generates a bunch of divs based on data I input into an array, however they all have the same class. The idea is that when one link is clicked it shows one of the ".catbox" divs and hides the rest.
All of these divs have the same class so I need to iterate through them, but I'm not quite sure how this is done with jQuery. Currently clicking on the last ".list" class triggers the on click event instead of all of them, and currently it shows all of the divs with the class ".catbox" instead of the corresponding one.
Here is the code:
var HTMLcatName = '<h1>%data%</h1>';
var HTMLcatImage = '<img id="cat" src="%data%">';
var HTMLcatCounter = '<p class="counter">Number of clicks: %data%</p>';
var HTMLcatList = '<p>%data%</p>'
var noCats = 'No cats selected m8';
var getCounterClass = document.getElementsByClassName("counter");
$(document).ready(function() {
cats.display();
$('.catbox').hide();
for (u = 0; u < cats.name.length; u++) {
formattedCatList = HTMLcatList.replace("%data%", cats.name[u]);
var listDiv = document.createElement('div');
listDiv.innerHTML = formattedCatList;
listDiv.className = "list";
$(".list").click(function() {
$(".catbox").toggle("slow");
});
$("body").prepend(listDiv);
}
});
var update = function() {
for (j = 0; j < getCounterClass.length; j++) {
getCounterClass[j].innerHTML = 'Number of clicks: ' + cats.clicks[j];
}
}
var cats = {
"name": ["Monte", "Jib"],
"image": ["images/monte.jpg", "images/jib.jpg"],
"clicks": [0, 0],
display: function () {
for (i = 0; i < cats.image.length; i++) {
formattedCatNames = HTMLcatName.replace("%data%", cats.name[i]);
formattedCatImages = HTMLcatImage.replace("%data%", cats.image[i]);
formattedCatCounter = HTMLcatCounter.replace("%data%", cats.clicks[i]);
var catDiv = document.createElement('div');
catDiv.className = "catbox";
catDiv.innerHTML = formattedCatNames + formattedCatImages + formattedCatCounter;
catDiv.querySelector('img').addEventListener('click', (function(catCountUp) {
return function() {
cats.clicks[catCountUp]++;
update();
};
})(i));
document.body.appendChild(catDiv);
}
},
}
The function I need help with is found within $(document).ready(function() {
Any help would be greatly appreciated.
The following can do it:
$(".list").on("click", function(){
$(this).find(".catbox").toggle("slow");
});
With $('.list') you get a group of elements of class list, so if you use $('.list').click(); you will bind the click event to just one element. You should use:
$(".list").each(function(){
$(this).click(function() {
$(".catbox").toggle("slow");
});
});

Clone element and repeat until viewport is filled

Learning JavaScript and this is my first ever real 'project''
I've created a loop to check that the .background element is smaller than the client, if it is then add the the slice node until the .background element is the same height as the client.
The issue is that too many slice nodes are being added and sometimes crashing the browser.
Also, is there a way to clone the HTML node and repeat it instead of using:
background.innerHTML += '<span class=slice></span>';
If is there a better way of doing what I'm trying to do then please do let me know!
JSFiddle
http://jsfiddle.net/8ryAD/11/
HTML
<div class="master">
<a id="btn-nav">
<span></span>
<span>Menu</span>
<span></span>
</a>
<nav id="main-nav">
<div class="background">
<div class="slice"></div>
</div>
</nav>
</div><!-- end master -->
JS
(function () {
'use strict';
var w = document.documentElement.clientHeight;
var menuBtn = document.getElementById('btn-nav');
var slice = document.querySelectorAll('.slice');
var background = document.getElementsByClassName('background')[0];
//var mainNavHeight = document.getElementById('main-nav').clientHeight;
//var slideAdd = slice.cloneNode(true);
//var windowHeight = window.innerHeight;
menuBtn.addEventListener('click', function() {
menuBtn.classList.add('open');
for(var i = background.clientHeight; i < w; i++) {
background.innerHTML += '<span class=slice></span>';
//for(var i = 0; i < slice.length; i++){
// slice[i].style.opacity = 1;
// }
}
}, false);
}());
Done it. If someone knows a better way of doing this then please do let me know! Thanks.
var slice = document.querySelectorAll('.slice');
var menuBtn = document.getElementById('btn-nav');
var b = document.getElementsByClassName('background')[0];
var a = document.getElementById('main-nav').clientHeight
menuBtn.addEventListener('click', function() {
menuBtn.classList.add('open');
for(var i = b.clientHeight; i < a; i++) {
if(b.clientHeight == a) {
break;
} else {
b.innerHTML += '<span class=slice></span>';
}
}
}, false);

Create dynamic divs using javascript/jquery

I would like to create dynamic divs using javascript or jquery and do not really know how to being.
<div id="clickable">
<button class="start">Default</button>
<button class="random">Randon</button>
<button class="gradient">Gradient</button>
<button class="trail">Trail</button>
<button class="clear">Clear</button>
<div id="start">Please click on one of the buttons to get started!</div>
</div>
<div class="wrapper">
</div>
I would like to add the x*x divs between the "wrapper" class. For example, when someone types 4, the output would be a 4x4 grid of divs. Thank you in advance!
Currently I have this, but nothing happens.
$(".start").click(function() {
total = prompt("Please enter a number");
});
$(document).ready(function() {
$(".start").click(function() {
function begin(total) {
for (var i = 0; i < total; i++) {
var rows = document.createElement("div");
for (var i = 1; i < rows; i++) {
var columns = document.createElement("div");
}
}
}
});
});
I have updated a fiddle here to get you started.
The way to get dynamic divs created is to first do the following
Get a handle to your container. In this case it will be $(".wrapper")
I don't know how you wanted the grid to be done, but I've done it by considering each row as one div (with 'n' rows), and each row containing 'n' column divs.
To create a div, you can use the handy $('<div>', {...}) notation. And as you go through the loop, do not forget to append to the container.
Keep the presentation in CSS (you can see that it has been done in the fiddle as well).
The code has been copied here for you to refer to.
$(".start").click(function () {
total = prompt("Please enter a number");
console.log("Total is", total);
//Now call the grid creator.
createGrid(total);
});
function createGrid(total) {
//Get the wrapper handle.
var $container = $(".wrapper");
//Loop through each row.
for (var rowIndex = 0; rowIndex < total; rowIndex++) {
//Create the skeleton for the row.
var rowId = "row-" + rowIndex; //Not needed.
var $row = $("<div>", {
"class": "row",
"id": rowId
});
//Loop through each column
for (var columnIndex = 0; columnIndex < total; columnIndex++) {
//Create skeleton for the column. (Note: IDs have to be unique)
var columnId = rowIndex + "-col-" + columnIndex; //Not needed.
var $column = $("<div>", {
"class": "column",
"id": columnId
});
//Append to the row div.
$row.append($column);
}
//Finally append this row to the container.
$container.append($row);
}
}

Categories

Resources