A grid is implemented using the CSS flexbox. Example:
The number of rows in this example is 4 because I fixed the container width for demo purposes. But, in reality, it can change based on container's width (e.g. if the user resizes the window). Try to resize the Output window in this example to get a feeling.
There is always one active item, marked with the black border.
Using JavaScript, I allow users to navigate to the previous/next item using the left/right arrow. In my implementation, I just decrease/increase the index of the active item by 1.
Now, I'd like to allow users to navigate up/down as well. For that, I just need to decrease/increase the index of the active item by <amount of items in a row>
. But, how do I calculate this number given that it is dependent on container's width? Is there a better way to implement the up/down functionality?
.grid {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
width: 250px;
height: 200px;
background-color: #ddd;
padding: 10px 0 0 10px;
}
.item {
width: 50px;
height: 50px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
For 3 items per row, add on the flex items: flex-basis: 33.333333% You can also use the flex 's shorthand like the following: flex: 0 0 33.333333% => which also means flex-basis: 33.333333% .
There is no method in flexbox to tell items in one row to line up with items in the row above — each flex line acts like a new flex container. It deals with space distribution across the main axis.
The main axis is defined by flex-direction , which has four possible values: row. row-reverse. column.
The question is slightly more complex than finding how many items are in a row.
Ultimately, we want to know if there's an element above, below, left, and right of the active element. And this needs to account for cases where the bottom row is incomplete. For example, in the case below, the active element has no item above, below, or right:
But, in order to determine if there's an item above/below/left/right of the active item, we need to know how many items are in a row.
To get the number of items per row we need:
itemWidth
- the outerWidth
of a single element including border
, padding
and margin
gridWidth
- the innerWidth
of the grid, excluding border
, padding
and margin
To calculate these two values with plain JavaScript we can use:
const itemStyle = singleItem.currentStyle || window.getComputedStyle(active);
const itemWidth = singleItem.offsetWidth + parseFloat(itemStyle.marginLeft) + parseFloat(itemStyle.marginRight);
const gridStyle = grid.currentStyle || window.getComputedStyle(grid);
const gridWidth = grid.clientWidth - (parseFloat(gridStyle.paddingLeft) + parseFloat(gridStyle.paddingRight));
Then we can calculate the number of elements per row using:
const numPerRow = Math.floor(gridWidth / itemWidth)
Note: this will only work for uniform-sized items, and only if the margin
is defined in px
units.
Dealing with all these widths, and paddings, margins, and borders is really confusing. There's a much, much, much simpler solution.
We only need to find the index of the grid element who's offsetTop
property is greater than the first grid element's offsetTop
.
const grid = Array.from(document.querySelector("#grid").children);
const baseOffset = grid[0].offsetTop;
const breakIndex = grid.findIndex(item => item.offsetTop > baseOffset);
const numPerRow = (breakIndex === -1 ? grid.length : breakIndex);
The ternary at the end accounts for the cases when there's only a single item in the grid, and/or a single row of items.
const getNumPerRow = (selector) => {
const grid = Array.from(document.querySelector(selector).children);
const baseOffset = grid[0].offsetTop;
const breakIndex = grid.findIndex(item => item.offsetTop > baseOffset);
return (breakIndex === -1 ? grid.length : breakIndex);
}
.grid {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
width: 400px;
background-color: #ddd;
padding: 10px 0 0 10px;
margin-top: 5px;
resize: horizontal;
overflow: auto;
}
.item {
width: 50px;
height: 50px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<button onclick="alert(getNumPerRow('#grid'))">Get Num Per Row</button>
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
To know if there's an item above or below the active element we need to know 3 parameters:
totalItemsInGrid
activeIndex
numPerRow
For example, in the following structure:
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
</div>
we have a totalItemsInGrid
of 5
, the activeIndex
has a zero-based index of 2
(it's the 3rd element in the group), and let's say the numPerRow
is 3.
We can now determine if there's an item above, below, left, or right of the active item with:
isTopRow = activeIndex <= numPerRow - 1
isBottomRow = activeIndex >= totalItemsInGid - numPerRow
isLeftColumn = activeIndex % numPerRow === 0
isRightColumn = activeIndex % numPerRow === numPerRow - 1 || activeIndex === gridNum - 1
If isTopRow
is true
we cannot move up, and if isBottomRow
is true
we cannot move down. If isLeftColumn
is true
we cannot move left, and if isRightColumn
if true
we cannot move right.
Note: isBottomRow
doesn't only check if the active element is on the bottom row, but also checks if there's an element beneath it. In our example above, the active element is not on the bottom row, but doesn't have an item beneath it.
I've worked this into a full example that works with resizing - and made the #grid
element resizable so it can be tested in the snippet below.
I've created a function, navigateGrid
that accepts three parameters:
gridSelector
- a DOM selector for the grid elementactiveClass
- the class name of the active elementdirection
- one of up
, down
, left
, or right
This can be used as 'navigateGrid("#grid", "active", "up")
with the HTML structure from your question.
The function calculates the number of rows using the offset
method, then does the checks to see if the active
element can be changed to the up/down/left/right element.
In other words, the function checks if the active element can be moved up/down and left/right. This means:
const navigateGrid = (gridSelector, activeClass, direction) => {
const grid = document.querySelector(gridSelector);
const active = grid.querySelector(`.${activeClass}`);
const activeIndex = Array.from(grid.children).indexOf(active);
const gridChildren = Array.from(grid.children);
const gridNum = gridChildren.length;
const baseOffset = gridChildren[0].offsetTop;
const breakIndex = gridChildren.findIndex(item => item.offsetTop > baseOffset);
const numPerRow = (breakIndex === -1 ? gridNum : breakIndex);
const updateActiveItem = (active, next, activeClass) => {
active.classList.remove(activeClass);
next.classList.add(activeClass);
}
const isTopRow = activeIndex <= numPerRow - 1;
const isBottomRow = activeIndex >= gridNum - numPerRow;
const isLeftColumn = activeIndex % numPerRow === 0;
const isRightColumn = activeIndex % numPerRow === numPerRow - 1 || activeIndex === gridNum - 1;
switch (direction) {
case "up":
if (!isTopRow)
updateActiveItem(active, gridChildren[activeIndex - numPerRow], activeClass);
break;
case "down":
if (!isBottomRow)
updateActiveItem(active, gridChildren[activeIndex + numPerRow], activeClass);
break;
case "left":
if (!isLeftColumn)
updateActiveItem(active, gridChildren[activeIndex - 1], activeClass);
break;
case "right":
if (!isRightColumn)
updateActiveItem(active, gridChildren[activeIndex + 1], activeClass);
break;
}
}
.grid {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
width: 400px;
background-color: #ddd;
padding: 10px 0 0 10px;
margin-top: 5px;
resize: horizontal;
overflow: auto;
}
.item {
width: 50px;
height: 50px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<button onClick='navigateGrid("#grid", "active", "up")'>Up</button>
<button onClick='navigateGrid("#grid", "active", "down")'>Down</button>
<button onClick='navigateGrid("#grid", "active", "left")'>Left</button>
<button onClick='navigateGrid("#grid", "active", "right")'>Right</button>
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
(For optimal experience better run the interactive snippets on full page)
Calculating number of elements per row
You need to get the width of an element with its margin (eventually border if they are set also) then you need to get the inner width of the container without padding. Having these 2 values you do a simple division to get the number of element per row.
Don't forget to consider the case where you have only one row, so you need to get the minimum value between the total number of elements and the number you get from the division.
//total number of element
var n_t = document.querySelectorAll('.item').length;
//width of an element
var w = parseInt(document.querySelector('.item').offsetWidth);
//full width of element with margin
var m = document.querySelector('.item').currentStyle || window.getComputedStyle(document.querySelector('.item'));
w = w + parseInt(m.marginLeft) + parseInt(m.marginRight);
//width of container
var w_c = parseInt(document.querySelector('.grid').offsetWidth);
//padding of container
var c = document.querySelector('.grid').currentStyle || window.getComputedStyle(document.querySelector('.grid'));
var p_c = parseInt(c.paddingLeft) + parseInt(c.paddingRight);
//nb element per row
var nb = Math.min(parseInt((w_c - p_c) / w),n_t);
console.log(nb);
window.addEventListener('resize', function(event){
//only the width of container will change
w_c = parseInt(document.querySelector('.grid').offsetWidth);
nb = Math.min(parseInt((w_c - p_c) / w),n_t);
console.log(nb);
});
.grid {
display: flex;
flex-wrap: wrap;
resize:horizontal;
align-content: flex-start;
background-color: #ddd;
padding: 10px 0 0 10px;
}
.item {
width: 80px;
height: 80px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
Here is a jQuery version of the same logic with less of code:
//total number of element
var n_t = $('.item').length;
//full width of element with margin
var w = $('.item').outerWidth(true);
//width of container without padding
var w_c = $('.grid').width();
//nb element per row
var nb = Math.min(parseInt(w_c / w),n_t);
console.log(nb);
window.addEventListener('resize', function(event){
//only the width of container will change
w_c = $('.grid').width();
nb = Math.min(parseInt(w_c / w),n_t);
console.log(nb);
});
.grid {
display: flex;
flex-wrap: wrap;
resize:horizontal;
align-content: flex-start;
background-color: #ddd;
padding: 10px 0 0 10px;
}
.item {
width: 80px;
height: 80px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
And here is a demonstration of the interactive grid:
var all = document.querySelectorAll('.item');
var n_t = all.length;
var current = 0;
all[current].classList.add('active');
var w = parseInt(document.querySelector('.item').offsetWidth);
var m = document.querySelector('.item').currentStyle || window.getComputedStyle(document.querySelector('.item'));
w = w + parseInt(m.marginLeft) + parseInt(m.marginRight);
var w_c = parseInt(document.querySelector('.grid').offsetWidth);
var c = document.querySelector('.grid').currentStyle || window.getComputedStyle(document.querySelector('.grid'));
var p_c = parseInt(c.paddingLeft) + parseInt(c.paddingRight);
var nb = Math.min(parseInt((w_c - p_c) / w),n_t);
window.addEventListener('resize', function(e){
w_c = parseInt(document.querySelector('.grid').offsetWidth);
nb = Math.min(parseInt((w_c - p_c) / w),n_t);
});
document.addEventListener('keydown',function (e) {
e = e || window.event;
if (e.keyCode == '38') {
if(current - nb>=0) {
all[current].classList.remove('active');
current-=nb;
all[current].classList.add('active');
}
}
else if (e.keyCode == '40') {
if(current + nb<n_t) {
all[current].classList.remove('active');
current+=nb;
all[current].classList.add('active');
}
}
else if (e.keyCode == '37') {
if(current>0) {
all[current].classList.remove('active');
current--;
all[current].classList.add('active');
}
}
else if (e.keyCode == '39') {
if(current<n_t-1) {
all[current].classList.remove('active');
current++;
all[current].classList.add('active');
}
}
});
.grid {
display: flex;
flex-wrap: wrap;
resize:horizontal;
align-content: flex-start;
background-color: #ddd;
padding: 10px 0 0 10px;
}
.item {
width: 80px;
height: 80px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
We can also consider another way to navigate inside the grid without the need of the number of element per row. The idea is to rely on the function elementFromPoint(x,y)
.
The logic is as follows: We are inside an active element and we have its (x,y)
position. By pressing a key we will increase/decrease these values and we use the above function to get the new element using the new (x,y)
. We test if we get a valid element and if this element is an item (contains item
class). In this case we remove active from the previous one and we add it to the new one.
Here is a example where I only consider an inside navigation. When we reach left/right boundary of the container we will not get to previous/next line:
var a = document.querySelector('.item');
a.classList.add('active');
var off = a.getBoundingClientRect();
/* I get the center position to avoid any potential issue with boundaries*/
var y = off.top + 40;
var x = off.left + 40;
document.addEventListener('keydown', function(e) {
e = e || window.event;
if (e.keyCode == '38') {
var elem = document.elementFromPoint(x, y - 90 /* width + both margin*/);
if (elem &&
elem.classList.contains('item')) {
document.querySelector('.active').classList.remove('active');
elem.classList.add('active');
y -= 90;
}
} else if (e.keyCode == '40') {
var elem = document.elementFromPoint(x, y + 90);
if (elem &&
elem.classList.contains('item')) {
document.querySelector('.active').classList.remove('active');
elem.classList.add('active');
y += 90;
}
} else if (e.keyCode == '37') {
var elem = document.elementFromPoint(x - 90, y);
if (elem &&
elem.classList.contains('item')) {
document.querySelector('.active').classList.remove('active');
elem.classList.add('active');
x -= 90;
}
} else if (e.keyCode == '39') {
var elem = document.elementFromPoint(x + 90, y);
if (elem &&
elem.classList.contains('item')) {
document.querySelector('.active').classList.remove('active');
elem.classList.add('active');
x += 90;
}
}
});
window.addEventListener('resize', function(e) {
var off = document.querySelector('.active').getBoundingClientRect();
y = off.top + 40;
x = off.left + 40;
});
.grid {
display: flex;
flex-wrap: wrap;
resize: horizontal;
align-content: flex-start;
background-color: #ddd;
padding: 10px 0 0 10px;
}
.item {
width: 80px;
height: 80px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
As you may notice in this method, we don't need any information about the container, the screen size, the number of element, etc. The only needed information is the dimension of a single item. We also need a small code to rectify the position of the active element on window resize.
Here is another fancy idea if you want to have a visually active element without the need of adding a class or getting it with JS. The idea is to use background on the container to create a black box behind the active element.
By the way, this method has 2 drawbacks:
Here is a simplified code with a fixed height/width container:
var grid = document.querySelector('.grid');
document.addEventListener('keydown', function(e) {
e = e || window.event;
if (e.keyCode == '38') {
var y = parseInt(grid.style.backgroundPositionY);
y= (y-90 + 270)%270;
grid.style.backgroundPositionY=y+"px";
} else if (e.keyCode == '40') {
var y = parseInt(grid.style.backgroundPositionY);
y= (y+90)%270;
grid.style.backgroundPositionY=y+"px";
} else if (e.keyCode == '37') {
var x = parseInt(grid.style.backgroundPositionX);
x= (x-90 + 270)%270;
grid.style.backgroundPositionX=x+"px";
} else if (e.keyCode == '39') {
var x = parseInt(grid.style.backgroundPositionX);
x= (x+90)%270;
grid.style.backgroundPositionX=x+"px";
}
});
.grid {
display: flex;
flex-wrap: wrap;
width:270px;
resize: horizontal;
align-content: flex-start;
background-color: #ddd;
padding: 10px 0 0 10px;
background-image:linear-gradient(#000,#000);
background-size:90px 90px;
background-repeat:no-repeat;
}
.item {
width: 80px;
height: 80px;
background-color: red;
margin: 0 10px 10px 0;
}
<div id="grid" class="grid" style="background-position:5px 5px;">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
As we can see the code is pretty simple so it can be suitable for such situation where almost all the values are known and fixed.
The only way to move around up and down that arises less unwanted complication to my knowledge is having the count of boxes per row and changing the indexes. The only problem is you need to calculate the boxcount on both window load and resize event.
var boxPerRow=0;
function calculateBoxPerRow(){}
window.onload = calculateBoxPerRow;
window.onresize = calculateBoxPerRow;
Now if you want a very simple way to get the number of boxes in a row without even caring about the size of neither the container nor the boxes, forget margins and paddings, you can check how many boxes are aligned with the first box comparing the offsetTop property.
The HTMLElement.offsetTop read-only property returns the distance of the current element relative to the top of the offsetParent node. [source: developer.mozilla.orgl]
You can implement it like below:
function calculateBoxPerRow(){
var boxes = document.querySelectorAll('.item');
if (boxes.length > 1) {
var i = 0, total = boxes.length, firstOffset = boxes[0].offsetTop;
while (++i < total && boxes[i].offsetTop == firstOffset);
boxPerRow = i;
}
}
Full working example:
(function() {
var boxes = document.querySelectorAll('.item');
var boxPerRow = 0, currentBoxIndex = 0;
function calculateBoxPerRow() {
if (boxes.length > 1) {
var i = 0,
total = boxes.length,
firstOffset = boxes[0].offsetTop;
while (++i < total && boxes[i].offsetTop == firstOffset);
boxPerRow = i;
}
}
window.onload = calculateBoxPerRow;
window.onresize = calculateBoxPerRow;
function focusBox(index) {
if (index >= 0 && index < boxes.length) {
if (currentBoxIndex > -1) boxes[currentBoxIndex].classList.remove('active');
boxes[index].classList.add('active');
currentBoxIndex = index;
}
}
document.body.addEventListener("keyup", function(event) {
switch (event.keyCode) {
case 37:
focusBox(currentBoxIndex - 1);
break;
case 39:
focusBox(currentBoxIndex + 1);
break;
case 38:
focusBox(currentBoxIndex - boxPerRow);
break;
case 40:
focusBox(currentBoxIndex + boxPerRow);
break;
}
});
})();
.grid {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
width: 50%;
height: 200px;
background-color: #ddd;
padding: 10px 0 0 10px;
}
.item {
width: 50px;
height: 50px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<div>[You need to click on this page so that it can recieve the arrow keys]</div>
<div id="grid" class="grid">
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
To support moving up, down, left, and right, you don't need to know how many boxes there are in a row, you just need to calculate if there is a box above, below, left, or right of the active box.
Moving left and right is simple as you've noticed - just check if the active box has a previousSiblingElement
or nextSiblingElement
. For up and down, you can use the current active box as an anchor point and compare it to the other box's getBoundingClientRect()
s, a DOM method which returns the geomoetry of an element relative to the browser viewport.
When trying to move up, start at the anchor and count down through the items towards 0. When moving down, start at the anchor and count until the end of the number of items. This is because when moving up, we only care about boxes before the active box, and when going down we only care about boxes after it. All we need to look for is a box that has the same left position with a higher or lower top position.
Below is an example which listens for a keydown event on window
and will move the active state according to which arrow key was pressed. It could definitely be made more DRY, but I've divided up the the four cases so you can see the exact logic in each. You can hold the arrow keys down so the box moves continuously and you can see it's very performant. And I've updated your JSBin with my solution here:http://jsbin.com/senigudoqu/1/edit?html,css,js,output
const items = document.querySelectorAll('.item');
let activeItem = document.querySelector('.item.active');
function updateActiveItem(event) {
let index;
let rect1;
let rect2;
switch (event.key) {
case 'ArrowDown':
index = Array.prototype.indexOf.call(items, activeItem);
rect1 = activeItem.getBoundingClientRect();
for (let i = index; i < items.length; i++) {
rect2 = items[i].getBoundingClientRect();
if (rect1.x === rect2.x && rect1.y < rect2.y) {
items[i].classList.add('active');
activeItem.classList.remove('active');
activeItem = items[i];
return;
}
}
break;
case 'ArrowUp':
index = Array.prototype.indexOf.call(items, activeItem);
rect1 = activeItem.getBoundingClientRect();
for (let i = index; i >= 0; i--) {
rect2 = items[i].getBoundingClientRect();
if (rect1.x === rect2.x && rect1.y > rect2.y) {
items[i].classList.add('active');
activeItem.classList.remove('active');
activeItem = items[i];
return;
}
}
break;
case 'ArrowLeft':
let prev = activeItem.previousElementSibling;
if (prev) {
prev.classList.add('active');
activeItem.classList.remove('active');
activeItem = prev;
}
break;
case 'ArrowRight':
let next = activeItem.nextElementSibling;
if (next) {
next.classList.add('active');
activeItem.classList.remove('active');
activeItem = next;
}
break;
default:
return;
}
}
window.addEventListener('keydown', updateActiveItem);
.grid {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
background-color: #ddd;
padding: 10px 0 0 10px;
}
.item {
width: 50px;
height: 50px;
background-color: red;
margin: 0 10px 10px 0;
}
.active.item {
outline: 5px solid black;
}
<div id="grid" class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item active"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With