How to Write a High Performance Seamless Scroll Plugin? - javascript

<pre>
<code>
<template>
<div class="seamless-group" ref="group">
<ul>
<li v-for="item in data" :key="item._id">
<img :src="item.pic" />
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
data: {
default: [],
type: Array
}
},
mounted() {
this.seamlessScroll({
pd: 40,
gridWidth: 556
});
},
methods: {
seamlessScroll(options = {}) {
let defaultConfig = {
pd: 20,
gridWidth: null,
xCache: 0
};
options = Object.assign(defaultConfig,options);
let el = this.$refs.group.querySelector("ul");
let parentContainerWidth = 0;
let imgContainers = el.querySelectorAll("li");
let count = imgContainers.length;
if(!options.gridWidth) {
for(let i = 0; i < count ; i++) {
parentContainerWidth += imgContainers[i].getBoundingClientRect().width;
el.insertAdjacentElement("beforeend",imgContainers[i].cloneNode(true));
}
}else {
for(let i = 0; i < count ; i++) {
parentContainerWidth += options.gridWidth;
el.insertAdjacentElement("beforeend",imgContainers[i].cloneNode(true));
}
}
parentContainerWidth = parentContainerWidth + count * options.pd;
console.log(parentContainerWidth);
options.xCache = el.querySelector("li:first-child").offsetLeft;
let fz_left = parentContainerWidth * -1;
scroll();
function scroll() {
if(options.xCache <= fz_left) {
options.xCache = 0;
}
options.xCache -= 1;
el.style.transform = "translateX(" + options.xCache.toString() + "px)";
window.requestAnimationFrame(scroll);
}
}
}
}
</script>
<style lang="less">
.seamless-group {
position: relative;
overflow: hidden;
ul {
list-style: none;
padding: 0;
display: flex;
li {
width: 556px;
margin-right: 40px;
}
}
}
</style>
</code>
This is a vue component code, I tried to encapsulate it, I used translate mobile parent container, do not know whether it is the appropriate method?there is a problem, such as the screen width is too large second scroll content can not be fully covered, how can I effectively solve the problem?(This is the code for a vue.js component).
This is my code:

Related

using JS to make tic tac toe

I have made a tic tac toe using JS however it has some problems My code is:
let x = [];
let o = [];
let xTurn = false;
let winPat = [
['00', '10', '20'],
['01', '11', '21'],
['02', '12', '22'],
['00', '01', '02'],
['10', '11', '12'],
['20', '21', '22'],
['00', '11', '22'],
['02', '11', '20']
];
const tableR = document.getElementsByClassName('tableR');
const button = document.getElementById('btn')
for (let i = 0; i <= 2; i++) {
for (let j = 0; j <= 2; j++) {
let newElement = document.createElement('td');
tableR.item(i).appendChild(newElement);
newElement.addEventListener('click', () => {
if (elementIsEmpty(newElement)) {
const img = document.createElement('img');
if (xTurn) {
img.src = 'https://www.google.com/url?sa=i&url=https%3A%2F%2Fcommons.wikimedia.org%2Fwiki%2FFile%3ARed_X.svg&psig=AOvVaw1B-fppvlN2x5oSCGllnXGc&ust=1634565535566000&source=images&cd=vfe&ved=0CAkQjRxqFwoTCMjbg6XN0fMCFQAAAAAdAAAAABAD';
x.push(String(i) + String(j))
} else {
img.src = 'https://www.google.com/imgres?imgurl=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fthumb%2Fd%2Fd0%2FLetter_o.svg%2F407px-Letter_o.svg.png&imgrefurl=https%3A%2F%2Fcommons.wikimedia.org%2Fwiki%2FFile%3ALetter_o.svg&tbnid=p-B77Lz3DDttwM&vet=12ahUKEwizlYTWzdHzAhXMg9gFHTSaBJkQMygAegUIARDaAQ..i&docid=K2HPZsIMOu4d5M&w=407&h=768&q=pixture%20of%20o&ved=2ahUKEwizlYTWzdHzAhXMg9gFHTSaBJkQMygAegUIARDaAQ';
o.push(String(i) + String(j))
}
newElement.append(img)
checkWinOrDraw(xTurn)
xTurn = !xTurn
}
})
}
}
const td = document.getElementsByTagName('td')
button.addEventListener('click', () => {
reset();
});
function elementIsEmpty(el) {
return (/^(\s| )*$/.test(el.innerHTML));
}
function checkWinOrDraw(xTurn) {
for (let i = 0; i < winPat.length; i++) {
if (xTurn) {
if (winPat[i].every(element => x.includes(element))) {
alert('X wins')
reset()
return
}
} else {
if (winPat[i].every(element => o.includes(element))) {
alert('O wins')
reset()
return
}
}
}
for (let item of td) {
if (elementIsEmpty(item))
return
}
alert('Draw')
reset()
}
function reset() {
x = [];
o = [];
for (let item of td) {
item.textContent = ''
}
}
body {
margin: 0px;
background-color: #3c4552;
color: aliceblue;
text-align: center;
}
header {
height: 75px;
background-color: #1f1e1c;
padding: 20px;
font-size: large;
}
table {
border-collapse: collapse;
margin: 40px auto;
}
td {
border: 7px solid black;
height: 121px;
width: 121px;
cursor: pointer;
}
button {
background-color: #1f1e1c;
color: white;
width: 25%;
height: 50px;
font-size: larger;
border: black solid 2px;
border-radius: 7px;
cursor: pointer;
}
img {
display: block;
width: 100%;
height: 100%;
}
<header>
<h1>TicTacToe</h1>
</header>
<main>
<table>
<tr class="tableR"></tr>
<tr class="tableR"></tr>
<tr class="tableR"></tr>
</table>
</main>
<footer>
<button id="btn">RESET</button>
</footer>
my problem is that when the game ends, say X wins, the image is not rendered on the screen, and alert() is called. I googled a lot and found no solutions.
details and clarity:
the image is not rendered on screen at the end of the game. try on your computer and see. alert() is called before the image is present in-game which results in a bad user experience. hence I need a solution.
Here your append() function is not able to complete image load & your checkWinOrDraw() function got called that's why this is happening.
Please try with the below code i am calling the checkWinOrDraw() once image gets loaded or failed.
for (let i = 0; i <= 2; i++) {
for (let j = 0; j <= 2; j++) {
let newElement = document.createElement('td');
tableR.item(i).appendChild(newElement);
newElement.addEventListener('click', () => {
if (elementIsEmpty(newElement)) {
const img = document.createElement('img');
if (xTurn) {
img.src = 'https://dummyimage.com/20x20/3a4ca6/fff';
x.push(String(i) + String(j))
} else {
img.src = 'https://dummyimage.com/30x30/fff/fff';
o.push(String(i) + String(j))
}
console.log(xTurn)
xTurn = !xTurn
img.addEventListener('load', checkWinOrDraw)
img.addEventListener('error', checkWinOrDraw)
newElement.appendChild(img)
}
})
}
}
function checkWinOrDraw(e,xTurn) {
//console.log('checking',xTurn)
for (let i = 0; i < winPat.length; i++) {
if (xTurn) {
if (winPat[i].every(element => x.includes(element))) {
alert('X wins')
reset()
return
}
} else {
if (winPat[i].every(element => o.includes(element))) {
alert('O wins')
reset()
return
}
}
}
for (let item of td) {
if (elementIsEmpty(item))
return
}
alert('Draw')
reset()
}

How to scroll to the next video on user scroll

i am building a video sharing app with ionic, a lot of videos on the page and each video is occupying almost all the height of the page, what i want to achieve is that when the user scrolls up, it will automatically place the next video at the bottom at video point of the screen and when the user scrolls down, it will automatically place the previous video at viewpoint of the screen.
Here is my code
the video.page.scss
the video.page.html
<div class="DivFyll DivVid" id="deyDiv_{{i}}" *ngFor="let video of videos; let i=index">
<video class="video" [autoplay]="i==0?true:false" [poster]="service.ApiURL+video.thumbs_path" #player playsinline [id]="i" loop preload="auto">
<source [src]="service.ApiURL+video.vid_path" type="video/mp4" />
</video>
</div>
the video.page.scss
ion-content {
overflow-y: auto;
width: 100%;
height: 100%;
display: block;
--background-color: transparent !important;
.DivFyll {
width: 100%;
height: 100%;
}
.DivVid {
position: relative;
}
video {
background-color: transparent;
width: 100%;
display: block;
height: 80%;
object-fit: fill;
margin-top: auto;
margin-bottom: auto;
}
}
Please does anyone knows how i can implement this with javascript or ionic
This Scroller Component Might Help:
Create Scroll Component Like this:
Create EventListener For Scroll, Then When User Scroll You get the event and with translate 3d or anything you want change the content of scroller component.
with ng-content you can embed any other component inside scroller component.
Html File:
<ng-content></ng-content>
TS File:
export class ScrollerComponent implements OnInit, OnDestroy {
#Output() scrollChange: EventEmitter<ScrollerEventArgs> = new EventEmitter();
#ViewChild('content') content: ElementRef;
scrollYPos: number = 0;
scrollXPos: number = 0;
prevScrollYPos: number = 0;
prevScrollXPos: number = 0;
element: HTMLElement;
scrollLength: number;
start: number;
end: number;
private previousStart: number;
private previousEnd: number;
private rowHeightCache: RowHeightCache = new RowHeightCache();
private scrollListener: any;
constructor(element: ElementRef, private ngZone: NgZone) {
this.element = element.nativeElement;
}
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
this.scrollListener = this.onScrolled.bind(this);
this.element.addEventListener('scroll', this.scrollListener);
});
}
ngOnDestroy() {
this.element.removeEventListener('scroll', this.scrollListener);
}
onScrolled(event: MouseEvent) {
// debugger
const dom: Element = event.currentTarget as Element;
this.scrollYPos = dom.scrollTop;
this.scrollXPos = dom.scrollLeft;
let direction = null;
if (this.scrollYPos < this.prevScrollYPos) {
direction = 'up';
} else if (this.scrollYPos > this.prevScrollYPos) {
direction = 'down';
}
if (this.prevScrollYPos !== this.scrollYPos || this.prevScrollXPos !== this.scrollXPos) {
if (direction && this.virtualScroll) {
this.chunkRows();
let topPadding = this.rowHeight * this.start;
if (this.rowHeightProp) {
topPadding = this.rowHeightCache.getRowOffset(this.start - 1);
}
this.ngZone.runOutsideAngular(() => {
requestAnimationFrame(() => {
this.content.nativeElement.style.transform = `translateY(${topPadding}px)`;
});
});
}
this.scrollChange.emit({
direction,
scrollYPos: this.scrollYPos,
scrollXPos: this.scrollXPos
});
this.prevScrollYPos = this.scrollYPos;
this.prevScrollXPos = this.scrollXPos;
} else if (this.scrollXPos === 0 ) {
this.scrollChange.emit({
direction,
scrollYPos: this.scrollYPos,
scrollXPos: this.scrollXPos
});
}
}
}
Update: RowHeightCache Class Added
export class RowHeightCache {
private cache: number[] = [];
calcScrollLength(totalRecords: number) {
return this.cache[totalRecords - 1];
}
initCache(rows: any[], rowHeightProp: string) {
const size = rows.length;
this.cache = new Array(size);
for (let i = 0; i < size; ++i) {
this.cache[i] = 0;
}
rows.forEach((row, i) => {
for (let index = i; index < size; index++) {
this.cache[index] += row[rowHeightProp];
}
});
}
calcRowIndex(offsetY: number): number {
if (offsetY === 0) {
return 0;
}
let pos = -1;
const dataLength = this.cache.length;
for (let i = dataLength; i >= 0; i--) {
const nextPos = pos + i;
if (nextPos < dataLength && offsetY >= this.cache[nextPos]) {
offsetY -= this.cache[nextPos];
pos = nextPos;
}
}
return pos + 1;
}
getRowOffset(rowIndex: number) {
if (rowIndex < 0) {
return 0;
}
return this.cache[rowIndex];
}
}

CSS Transition not working with Javascript on web component

i'm new on Javascript and i'm trying to create a web component, precisely some kind of list view. The goal would be it can expand and collapse when I click an item, and I'd like it to do with transitions.
I already put the transition on the template, but for some reason, it doesn't work, just grow or collapse instantly without the animation.
Rarely, if I do a const listView = document.querySelector(".list-view") and then listView.collapse() it works.
The summary code is:
class ListItem extends HTMLElement {
constructor(items = []) {
super();
//Template and its variables
const listItemTemplate = document.createElement("template");
listItemTemplate.innerHTML = `
<style>
.listitem{
//some other properties
transition: height 0.3s cubic-bezier(0.65, 0, 0.35, 1);
height: ${this.initialHeight};
}
//Other styles...
</style>
changedItem(itemSelected){
//More stuff
this.refreshDOMItems();
this.collsapse();
}
`;
expand() {
let height = 10;
Array.from(this.itemsHTML).forEach((item) => {
height += item.clientHeight;
});
this.listItem.style.height = height / 16 + "rem";
}
collapse() {
this.listItem.style.height = this.initialHeight;
}
Edit: Here is a Codepen.
https://codepen.io/salvabarg/pen/zYqJNMj
What i am doing wrong? Hope the code is not much caotic. Thank you in advance!
You are overriding the height transition with transition: background .5s ease; on .listitem:hover, so removing this line solves the problem:
.listitem:hover{
/*transition: background .5s ease;*/
background: rgba(255,255,255,.65);
}
class ListItem extends HTMLElement {
constructor(items = []) {
super();
//Template and its variables
this.initialHeight = "3.75rem";
this.initialBorderRadius = stringToNumber(this.initialHeight) / 2 + "rem";
const listItemTemplate = document.createElement("template");
listItemTemplate.innerHTML = `
<style>
ul{
box-sizing: border-box;
margin: 0px;
padding: 0px;
list-style: none;
}
.ul{
display: flex;
align-items: center;
flex-direction: column;
height: 3.75rem;
}
.listitem{
font-family: 'Nunito', sans-serif;
display: inline-block;
overflow: hidden;
box-sizing: border-box;
background-color: white;
border-radius: ${this.initialBorderRadius};
padding: 0px 1rem;
cursor: pointer;
box-shadow: 0px 0px 1.25rem rgba(4,25,106,.14);
transition: height 0.3s cubic-bezier(0.65, 0, 0.35, 1);
height: ${this.initialHeight};
}
li.item{
box-sizing: border-box;
min-height: ${this.initialHeight};
display: flex;
align-items: center;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome, Edge, Opera and Firefox */
}
.img{
width: 2rem;
margin-right: 0.625rem;
border-radius: 50%;
}
.name{
font-size: 1.125rem;
font-weight: 700;
color: var(--autores);
margin-right: 0.6rem;
}
.listitem:hover{
/*transition: background .5s ease;*/
background: rgba(255,255,255,.65);
}</style>
<div class="listitem">
<ul class="ul">
</ul>
</div>
`;
//Constructor
this.attachShadow({
mode: "open"
});
this.shadowRoot.appendChild(listItemTemplate.content.cloneNode(true));
this.items = items;
this.listItem = this.shadowRoot.querySelector(".listitem");
this.itemsHTML = this.shadowRoot
.querySelector(".listitem")
.querySelector(".ul").children;
const ul = this.shadowRoot.querySelector(".listitem").querySelector(".ul");
this.ul = ul;
}
connectedCallback() {
//Do
//Carga de items por defecto
const item = {
name: "Item",
avatar: "images/users.svg",
selected: false
};
const item_two = {
name: "Item 2",
avatar: "images/users.svg",
selected: false
};
const item_three = {
name: "Item 3",
avatar: "images/users.svg",
selected: false
};
this.addItem(item);
this.addItem(item_two);
this.addItem(item_three);
this.refreshDOMItems();
//event listeners for each item;
const itemClick = this.shadowRoot.querySelector(".listitem");
itemClick.addEventListener("click", (event) => {
event.preventDefault();
let targetStr = "";
const trgtCls = event.target.classList;
if (
trgtCls.contains("name") ||
trgtCls.contains("img") ||
trgtCls.contains("item")
) {
if (trgtCls.contains("name")) {
targetStr = event.target.innerText;
}
if (trgtCls.contains("img")) {
targetStr = event.target.nextElementSibling.innerText;
}
if (trgtCls.contains("item")) {
targetStr = event.target.querySelector(".name").innerText;
}
}
if (targetStr === this.items[0].name) {
this.expand();
} else {
this.changedItem(targetStr);
}
});
}
addItem(item = Object) {
this.items.push(item);
this.items.forEach((item) => {
item.selected = false;
});
this.items[0].selected = true;
console.log(item.selected);
}
refreshDOMItems() {
removeChildNodes(this.ul);
this.items.forEach((item) => {
const itemTemplate = document.createElement("template");
itemTemplate.innerHTML = `
<li class="item">
<svg viewBox="0 0 512 512" width="100" title="user-alt" class="img">
<path d="M256 288c79.5 0 144-64.5 144-144S335.5 0 256 0 112 64.5 112 144s64.5 144 144 144zm128 32h-55.1c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16H128C57.3 320 0 377.3 0 448v16c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48v-16c0-70.7-57.3-128-128-128z" />
</svg>
<p class="name">${item.name}</p>
</li>
`;
this.ul.appendChild(itemTemplate.content.cloneNode(true));
});
}
getItems() {
return this.items;
}
changedItem(itemSelected) {
let arr = Array.from(this.items);
this.items.forEach(function(item, index) {
if (item.name == itemSelected) {
arr = moveElementArray(arr, index, 0);
}
});
this.items = arr;
this.items.forEach((item) => {
item.selected = false;
});
this.items[0].selected = true;
this.refreshDOMItems();
this.collapse();
}
selected() {
let selected;
this.items.forEach((item) => {
if (item.selected === true) {
selected = item;
}
});
return selected;
}
value() {
let selected;
this.items.forEach((item) => {
if (item.selected === true) {
selected = item;
}
});
return selected.name;
}
expand() {
let height = 10;
Array.from(this.itemsHTML).forEach((item) => {
height += item.clientHeight;
});
this.listItem.style.height = height / 16 + "rem";
}
collapse() {
this.listItem.style.height = this.initialHeight;
}
}
window.customElements.define("c-list-item", ListItem);
const lsim = document.querySelector(".list");
function removeChildNodes(element = HTMLElement) {
while (element.childElementCount > 0) {
element.removeChild(element.firstChild);
}
}
function stringToNumber(string = String) {
let newNumber = "";
let afterComma = "";
let comma = false;
Array.from(string).forEach((char) => {
if (char === "." || char === ",") {
comma = true;
}
if (comma === false) {
if (Number(char)) {
newNumber += Number(char);
}
} else {
if (Number(char)) {
afterComma += Number(char);
}
}
});
if (afterComma != "") {
newNumber += "." + afterComma;
}
return Number(newNumber);
}
function moveElementArray(array = Array, from = Number, to = Number) {
const fromArr = array[from];
if (from > to) {
for (let index = from; index >= to; index--) {
array[index] = array[index - 1];
if (index == to) {
array[index] = fromArr;
}
}
} else {
for (let index = from; index <= to; index++) {
array[index] = array[index + 1];
if (index == to) {
array[index] = fromArr;
}
}
}
return array;
}
function replaceElementArray(array = Array, from = Number, to = Number) {
const fromArr = array[from];
const toArr = array[to];
array[from] = toArr;
array[to] = fromArr;
return array;
}
<body style="background-color: #f0f0f0;">
<c-list-item class="list"></c-list-item>
</body>

Javascript - Multiple File Uploader with Limit

This Javascript function allows for multiple file uploads and includes a file size limit, however it is missing a maximum number of files allowed to be uploaded.
The function handleFile(e) has the file type and size arguments passed through it but do not know where to introduce a limit on the allowable number of files to be uploaded.
const fInputs = document.querySelectorAll('.btcd-f-input>div>input')
function getFileSize(size) {
let _size = size
let unt = ['Bytes', 'KB', 'MB', 'GB'],
i = 0; while (_size > 900) { _size /= 1024; i++; }
return (Math.round(_size * 100) / 100) + ' ' + unt[i];
}
function delItem(el) {
fileList = { files: [] }
let fInp = el.parentNode.parentNode.parentNode.querySelector('input[type="file"]')
for (let i = 0; i < fInp.files.length; i++) {
fileList.files.push(fInp.files[i])
}
fileList.files.splice(el.getAttribute('data-index'), 1)
fInp.files = createFileList(...fileList.files)
if (fInp.files.length > 0) {
el.parentNode.parentNode.parentNode.querySelector('.btcd-f-title').innerHTML = `${fInp.files.length} File Selected`
} else {
el.parentNode.parentNode.parentNode.querySelector('.btcd-f-title').innerHTML = 'No File Chosen'
}
el.parentNode.remove()
}
function fade(element) {
let op = 1; // initial opacity
let timer = setInterval(function () {
if (op <= 0.1) {
clearInterval(timer);
element.style.display = 'none';
}
element.style.opacity = op;
element.style.filter = 'alpha(opacity=' + op * 100 + ")";
op -= op * 0.1;
}, 50);
}
function unfade(element) {
let op = 0.01; // initial opacity
element.style.opacity = op;
element.style.display = 'flex';
let timer = setInterval(function () {
if (op >= 1) {
clearInterval(timer);
}
element.style.opacity = op;
element.style.filter = 'alpha(opacity=' + op * 100 + ")";
op += op * 0.1;
}, 13);
}
function get_browser() {
let ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(M[1])) {
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
return { name: 'IE', version: (tem[1] || '') };
}
if (M[1] === 'Chrome') {
tem = ua.match(/\bOPR|Edge\/(\d+)/)
if (tem != null) { return { name: 'Opera', version: tem[1] }; }
}
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
if ((tem = ua.match(/version\/(\d+)/i)) != null) { M.splice(1, 1, tem[1]); }
return {
name: M[0],
version: M[1]
};
}
for (let inp of fInputs) {
inp.parentNode.querySelector('.btcd-inpBtn>img').src = ''
inp.addEventListener('mousedown', function (e) { setPrevData(e) })
inp.addEventListener('change', function (e) { handleFile(e) })
}
let fileList = { files: [] }
let fName = null
let mxSiz = null
function setPrevData(e) {
if (e.target.hasAttribute('multiple') && fName !== e.target.name) {
console.log('multiple')
fName = e.target.name
fileList = fileList = { files: [] }
if (e.target.files.length > 0) {
for (let i = 0; i < e.target.files.length; i += 1) {
console.log(e.target.files[i])
fileList.files.push(e.target.files[i])
}
}
}
}
function handleFile(e) {
let err = []
const fLen = e.target.files.length;
mxSiz = e.target.parentNode.querySelector('.f-max')
mxSiz = mxSiz != null && (Number(mxSiz.innerHTML.replace(/\D/g, '')) * Math.pow(1024, 2))
if (e.target.hasAttribute('multiple')) {
for (let i = 0; i < fLen; i += 1) {
fileList.files.push(e.target.files[i])
}
} else {
fileList.files.push(e.target.files[0])
}
//type validate
if (e.target.hasAttribute('accept')) {
let tmpf = []
let type = new RegExp(e.target.getAttribute('accept').split(",").join("$|") + '$', 'gi')
for (let i = 0; i < fileList.files.length; i += 1) {
if (fileList.files[i].name.match(type)) {
tmpf.push(fileList.files[i])
} else {
err.push('Wrong File Type Selected')
}
}
fileList.files = tmpf
}
// size validate
if (mxSiz > 0) {
let tmpf = []
for (let i = 0; i < fileList.files.length; i += 1) {
if (fileList.files[i].size < mxSiz) {
tmpf.push(fileList.files[i])
mxSiz -= fileList.files[i].size
} else {
console.log('rejected', i, fileList.files[i].size)
err.push('Max Upload Size Exceeded')
}
}
fileList.files = tmpf
}
if (e.target.hasAttribute('multiple')) {
e.target.files = createFileList(...fileList.files)
} else {
e.target.files = createFileList(fileList.files[fileList.files.length - 1])
fileList = { files: [] }
}
// set File list view
if (e.target.files.length > 0) {
e.target.parentNode.querySelector('.btcd-f-title').innerHTML = e.target.files.length + ' File Selected'
e.target.parentNode.parentNode.querySelector('.btcd-files').innerHTML = ''
for (let i = 0; i < e.target.files.length; i += 1) {
let img = null
if (e.target.files[i].type.match(/image-*/)) {
img = window.URL.createObjectURL(e.target.files[i])
}
else {
img = ''
}
e.target.parentNode.parentNode.querySelector('.btcd-files').insertAdjacentHTML('beforeend', `<div>
<img src="${img}" alt="img" title="${e.target.files[i].name}">
<div>
<span title="${e.target.files[i].name}">${e.target.files[i].name}</span>
<br/>
<small>${getFileSize(e.target.files[i].size)}</small>
</div>
<button type="button" onclick="delItem(this)" data-index="${i}" title="Remove This File"><span>×</span></button>
</div>`)
}
}
// set eror
if (err.length > 0) {
for (let i = 0; i < err.length; i += 1) {
e.target.parentNode.parentNode.querySelector('.btcd-files').insertAdjacentHTML('afterbegin', `
<div style="background: #fff2f2;color: darkred;display:none" class="btcd-f-err">
<img src="" alt="img">
<span>${err[i]}</span>
</div>`)
}
const errNods = e.target.parentNode.parentNode.querySelectorAll('.btcd-files>.btcd-f-err')
for (let i = 0; i < errNods.length; i += 1) {
unfade(errNods[i])
setTimeout(() => { fade(errNods[i]) }, 3000);
setTimeout(() => { errNods[i].remove() }, 4000);
}
err = []
}
}
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, Helvetica, sans-serif;
}
.btcd-f-input {
display: inline-block;
width: 340px;
position: relative;
overflow: hidden;
}
.btcd-f-input>div>input::-webkit-file-upload-button {
cursor: pointer;
}
.btcd-f-wrp {
cursor: pointer;
}
.btcd-f-wrp>small {
color: gray;
}
.btcd-f-wrp>button {
cursor: pointer;
background: #f3f3f3;
padding: 5px;
display: inline-block;
border-radius: 9px;
border: none;
margin-right: 8px;
height: 35px;
}
.btcd-f-wrp>button>img {
width: 24px;
}
.btcd-f-wrp>button>span,
.btcd-f-wrp>span,
.btcd-f-wrp>small {
vertical-align: super;
}
.btcd-f-input>.btcd-f-wrp>input {
z-index: 100;
width: 100%;
position: absolute;
opacity: 0;
left: 0;
height: 37px;
cursor: pointer;
}
.btcd-f-wrp:hover {
background: #fafafa;
border-radius: 10px;
}
.btcd-files>div {
display: flex;
align-items: center;
background: #f8f8f8;
border-radius: 10px;
margin-left: 30px;
width: 91%;
margin-top: 10px;
height: 40px;
}
.btcd-files>div>div {
display: inline-block;
width: 73%;
}
.btcd-files>div>div>small {
color: gray;
}
.btcd-files>div>img {
width: 40px;
height: 40px;
margin-right: 10px;
border-radius: 10px;
}
.btcd-files>div>div>span {
display: inline-block;
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.btcd-files>div>button {
background: #e8e8e8;
border: none;
border-radius: 50px;
width: 25px;
height: 25px;
font-size: 20px;
margin-right: 6px;
padding: 0;
}
.btcd-files>div>button:hover {
background: #bbbbbb;
}
<div class="btcd-f-input">
<small>Multiple Upload</small>
<div class="btcd-f-wrp">
<button class="btcd-inpBtn" type="button"> <img src="" alt=""> <span> Attach File</span></button>
<span class="btcd-f-title">No File Chosen</span>
<small class="f-max">(Max 1 MB)</small>
<input multiple type="file" name="snd_multiple" id="">
</div>
<div class="btcd-files">
</div>
</div>
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
<script src="https://unpkg.com/create-file-list#1.0.1/dist/create-file-list.min.js"></script>
In the function handleFile before type validate:
let maxFileNum = 10; // Maximum number of files
if (fileList.files.length > maxFileNum){
err.push("Too many files selected");
}

PUZZLE TROUBLE - trying to save initial board [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I wrote this Javascript application for a 15-puzzle. The entire application is contained in the file below. Whenever I render a new board, I'm trying to store the initial configuration in the initialBoard variable so I can replay the same game later. However, initialBoard variable always seems to equal the currentBoard variable. I'm new to Javascript and any help will be greatly appreciated.
<html>
<head>
<title>15 Puzzle</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<style type="text/css">
#puzzle-board {
border: 5px solid;
}
.puzzle-tile {
background:#fff;
background: -moz-linear-gradient(top, #fff, #eee);
background: -webkit-gradient(linear,0 0, 0 100%, from(#fff), to(#eee));
box-shadow: inset 0 0 0 1px #fff;
-moz-box-shadow: inset 0 0 0 1px #fff;
-webkit-box-shadow: inset 0 0 0 1px #fff;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size:60px;
height: 100px;
text-align: center;
text-decoration: none;
text-shadow:0 1px #fff;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
vertical-align:middle;
width: 100px;
}
#start-stop {
float: left;
}
#timer {
float: left;
margin-left: 10px;
}
#counter {
float: left;
margin-left: 10px;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<br />
</div>
<div class="row">
<div class="col-md-5 col-md-offset-1">
<table id="puzzle"></table>
</div>
<div class="col-md-6">
<div class="row">
<br />
<button type="button" class="btn btn-lg btn-success" id="start-stop">START</button>
<button type="button" class="btn btn-lg btn-default" id="timer"></button>
<button type="button" class="btn btn-lg btn-default" id="counter"></button>
</div>
</div>
</div>
</div>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="http://code.jquery.com/ui/1.11.1/jquery-ui.min.js"></script>
<!--<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>-->
<script>
/**
* Puzzle Object
*/
puzzle = function(targetId) {
/************************************************************
* Private members
************************************************************/
var
currentBoard,
initialBoard,
orderedBoard = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,'']];
function canSwapTiles(source, target) {
var sourceTileRow = source.attr("data-row");
var sourceTileCol = source.attr("data-col");
var sourceTileValue = source.text();
var targetTileRow = target.attr("data-row");
var targetTileCol = target.attr("data-col");
var targetTileValue = target.text();
if (sourceTileValue != '' && targetTileValue != '') {
return false;
} else if (Math.abs(targetTileRow - sourceTileRow) > 1) {
return false;
} else if (Math.abs(targetTileCol - sourceTileCol) > 1) {
return false;
} else {
return true;
}
}
function swapTiles(source, target) {
var sourceTileRow = source.attr("data-row");
var sourceTileCol = source.attr("data-col");
var sourceTileValue = source.text();
var targetTileRow = target.attr("data-row");
var targetTileCol = target.attr("data-col");
var targetTileValue = target.text();
source.text(targetTileValue);
currentBoard[sourceTileRow][sourceTileCol] = parseInt(targetTileValue);
target.text(sourceTileValue);
currentBoard[targetTileRow][targetTileCol] = parseInt(sourceTileValue);
$(targetId).trigger('moved');
console.log("swapped tiles");
console.log(initialBoard);
if (isSolved())
{
console.log('solved puzzle');
console.log(initialBoard);
$(targetId).trigger('solved', {
board: initialBoard
});
}
}
function renderBoard(board) {
$("#puzzle-board").empty();
currentBoard = board;
//initialBoard = board;
console.log('rendering board');
console.log(initialBoard);
for (i = 0; i < 4; i++) {
$("#puzzle-board").append('<tr class="puzzle-row" id="puzzle-row-' + i + '"></tr><br />');
for (j = 0; j < 4; j++) {
var tile = '<td class="puzzle-tile" data-row="' + i + '" data-col="' + j + '">' +
board[i][j] +
'</td>';
$("#puzzle-row-" + i).append(tile);
}
}
$(".puzzle-tile").draggable(
{
revert: true,
snap: true,
snapMode: "inner",
zIndex: 100
}
).droppable(
{
drop: function (event, ui) {
var sourceTile = ui.draggable;
var targetTile = $(this);
if (canSwapTiles(sourceTile, targetTile)) {
swapTiles(sourceTile, targetTile);
}
}
}
);
}
function randomBoard() {
var tileValues = [];
for (i = 0; i < 15; i++) {
tileValues[i] = i + 1;
}
var randomlyOrderedTileValues = [''];
do {
randomlyOrderedTileValues[(16 - tileValues.length)] = tileValues.splice(Math.floor(Math.random() * tileValues.length), 1).pop();
} while (tileValues.length > 0);
var board = [];
for (i = 0; i < 4; i++) {
board[i] = [];
for (j = 0; j < 4; j++) {
board[i][j] = randomlyOrderedTileValues.pop();
}
}
return board;
}
function isSolved() {
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
if (isNaN(currentBoard[i][j]))
{
continue;
}
if (parseInt(currentBoard[i][j]) != parseInt(orderedBoard[i][j]))
{
return false;
}
}
}
return true;
}
/************************************************************
* Constructor
************************************************************/
/*
* Initialize board
*/
$(targetId).append('<tbody id="puzzle-board"></tbody>');
renderBoard(orderedBoard);
/************************************************************
* Public data and methods
************************************************************/
return {
reset: function() {
renderBoard(orderedBoard);
},
shuffle: function() {
//initialBoard = randomBoard();
initialBoard = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,'',15]];
renderBoard(initialBoard);
}
}
};
/**
* Timer Object
*/
timer = function(targetId) {
/************************************************************
* Private members
************************************************************/
var
intervalId,
totalSeconds = 0;
function pad(val) {
var valString = val + "";
if (valString.length < 2) {
return "0" + valString;
} else {
return valString;
}
}
function setTime()
{
++totalSeconds;
$("#seconds").html(pad(totalSeconds % 60));
$("#minutes").html(pad(parseInt(totalSeconds / 60)));
}
/************************************************************
* Constructor
************************************************************/
/*
* Initialize timer
*/
$(targetId).append('<i>Time: </i><i id="minutes">00</i>:<i id="seconds">00</i>');
/************************************************************
* Public data and methods
************************************************************/
return {
reset: function() {
window.clearInterval(intervalId);
totalSeconds = 0;
$("#minutes").text('00');
$("#seconds").text('00');
},
start: function () {
intervalId = window.setInterval(setTime, 1000);
},
getTime: function () {
return pad(parseInt(totalSeconds / 60)) + ':' + pad(totalSeconds % 60);
}
}
};
/**
* Counter Object
*/
counter = function(targetId) {
/************************************************************
* Private members
************************************************************/
var
steps = 0;
/************************************************************
* Constructor
************************************************************/
/*
* Initialize timer
*/
$(targetId).append('<i id="steps-title">Steps: </i><i id="steps-count">0</i>');
/************************************************************
* Public data and methods
************************************************************/
return {
reset: function() {
steps = 0;
$("#steps-count").text(steps);
},
incr: function () {
steps++;
$("#steps-count").text(steps);
},
getSteps: function () {
return steps;
}
}
};
$(document).ready(function() {
var Puzzle = puzzle("#puzzle");
var Timer = timer("#timer");
var Counter = counter("#counter");
localStorage["games"] = '[]';
$("#start-stop").click(function() {
switch ($(this).text()) {
case 'START':
$(this).removeClass("btn-success").addClass("btn-danger").text("STOP");
Puzzle.shuffle();
Timer.start();
Counter.reset();
break;
case 'STOP':
$(this).removeClass("btn-danger").addClass("btn-success").text("START");
Puzzle.reset();
Timer.reset();
Counter.reset();
break;
}
});
$("#puzzle").bind('moved',
function(e, data) {
Counter.incr();
}
).bind('solved',
function(e, data) {
console.log(data);
$("#start-stop").removeClass("btn-danger").addClass("btn-success").text("START");
Puzzle.reset();
Timer.reset();
Counter.reset();
}
);
});
</script>
</body>
When you invoke the Puzzle.shuffle() here:
$("#start-stop").click(function() {
switch ($(this).text()) {
case 'START':
$(this).removeClass("btn-success").addClass("btn-danger").text("STOP");
Puzzle.shuffle();
Timer.start();
Counter.reset();
break;
case 'STOP':
$(this).removeClass("btn-danger").addClass("btn-success").text("START");
Puzzle.reset();
Timer.reset();
Counter.reset();
break;
}
});
It initializes the board and passes it to renderBoard here
shuffle: function() {
//initialBoard = randomBoard();
initialBoard = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,'',15]];
renderBoard(initialBoard);
}
Then renderBoard does this:
currentBoard = board;
Which causes both variables point to the same object. If you want them to be separated, then in renderBoard you should clone the object, instead of assigning it. Something along the lines of this if you use jQuery:
currentBoard = [];
$.extend(currentBoard, board);

Categories