I have a text element that is built when the user clicks a button. In the middle of that text, I added an input element.I want to make this process repeats seven times but it looks complicated. I actually made a lot of things but here are the important ones So, First I made an input element and a button when the user enter input and click the button what he wrote will be displayed in a h1 element with a random missing word Then,I made an input element in the that the missing word should've been in.What I want is that this to repeat this process seven times.
function myFunction() {
var x = document.getElementById("input").value;
x = x + " ";
var y = document.getElementById("inputed");
var a,b = "c";
const c=[];
const d=[];
var e;
var f = 0;
const array = [];
const z = x.length;
for (let index = 0; index < 7; index++) {
a= "c";
b= "c";
c.push(0);
d.push(0);
c[index] = 0;
d[index] = 0;
e = 1;
f = 0;
//Here the program will get a random space location and the space after it
while (a != " ") {
c[index] = Math.floor(Math.random() * x.length - 2);
a = x.charAt(c[index]);
}
d[index] = c[index];
//The variable d will be the same as c and will increase until its value corrspends to a space
while (b != " ") {
d[index]++;
b = x.charAt(d[index]);
}
//I want the y.innerHTML to write from the 0 to c[0] then display the input element then from d[0] //to c[1] and display the input element again and form d[1] to c[2] until 7
y.innerHTML = `${x.slice(
0,
c[0]
)}<input type="text" style="color: gray;border:2px solid;border-radius: 10px;outline: none;
font-size: x-large;height:40px;width: 100px;" >${x.slice(d[0], z)} `;
}
}
I had problems understanding perfectly your code so I couldn't answer trying to address the exact problems affecting your specific algorithm.
Anyway I made an attempt trying to understand your description and as far as I could get I tried to implement a demo beginning from a input text filled by the user and:
When the button PARSE SENTENCE gets clicked, the procedures parses the
input text splitting its content by white spaces to find out the
words.
Then it determines how many random words he wants to "remove" from
the string and at which index.
Then it creates a span element for each of those words and put all of them
in a separate array
Such spans, will have the word in their innerText, the default class
word and an added class that will be kept-word if that's a word not
to remove and removed-word otherwise. Plus they will keep track of that original word inside a data attribute called data-original
Those spans get appended to the #transformed element, and at this
stage, if the word belongs to the class .removed-word its
innerText gets emptied and its contenteditable attribute
activated.
In the end, after the parsing of the sentence, the exact spans code
will be shown in the <pre> element to give evidence of what's going
on
I know it's very far from what you have asked.. considering I didn't strictly stressed the algorithm aspect and mostly used shortcut like split, includes, contains and more importantly I totally changed your logics and used spans for words parsed and didn't use the input at all but contenteditable.
I sincerely hope it will anyway give some inspiration
function transform(){
const input = document.getElementById('input').value.trim();
const words = input.split(" ");
const nrOfWordsToReplace = Math.floor(Math.random() * words.length);
const indexesOfWordsToReplace
= Array.from({length: nrOfWordsToReplace}, () => {
return Math.floor(Math.random() * words.length);
});
//for each word in the input, create a span element to add in #transformed
//words expected to be kept will have their span with class .word.kept-word
//words expected to be removed will have their span with class .word.remove-word
let index = 0;
const outputWords = [];
for(word of words){
if( indexesOfWordsToReplace.includes(index) ){
const outputWord = createOutputWordElement(word, 'removed-word');
outputWords.push( outputWord );
}else{
const outputWord = createOutputWordElement(word, 'kept-word');
outputWords.push( outputWord );
}
index++;
}
const codeContainer = document.querySelector('pre');
codeContainer.innerHTML = '';
//clear its content appends those spans to #transform
const transformed = document.getElementById('transformed');
transformed.innerHTML = '';
for(outputWord of outputWords){
if (outputWord.classList.contains('removed-word')){
outputWord.innerText = '';
outputWord.contentEditable = true;
}
transformed.append(outputWord);
const code = document.createElement('code');
code.innerText = outputWord.outerHTML;
codeContainer.append(code);
}
}
//returns a span element having the word as content and classname among its classes
function createOutputWordElement(word, classname){
const outputWord = document.createElement('span');
outputWord.classList.add('word');
outputWord.classList.add(classname);
outputWord.dataset.original = word;
outputWord.innerText = word;
return outputWord;
}
body{
padding: 1rem;
}
*{
box-sizing: border-box;
}
button{
width: 100%;
margin-top: 1rem;
margin-bottom: 1rem;
padding: 1rem;
cursor: pointer;
text-transform: uppercase;
}
#input{
display: block;
width: 100%;
}
pre code{
display: block;
}
#transformed{
border: dashed 3px darkgray;
padding: 1rem;
}
.word{
border: solid 2px;
padding: .25rem;
margin-right: .50rem;
display: inline-block;
}
.word.kept-word{
border-color: darkgray;
}
.word.removed-word{
border-color: red;
cursor: pointer;
min-width: 2rem;
}
.
<input type="text" id="input">
<button type="button" onclick="transform();">Parse sentence</button>
<div id="transformed"></div>
<pre>
</pre>
Related
Im working on a project and i have basically some troubles with things for my website.
This one is a bit hard for me, i have some ideas but i dont know how to do them in my javascript code.
I have 98 divs (it's a calendar where you can select everyday differents hours to book slots).
There is a Summary (kinda same thing on commercial website) which i want that it says how many slots you selected. But the problem is that i have like I said 98div so i wanna do it in one function.
On the slots you want to book, you can click on it (it selects it) and if you click on it again it deselects it.
I want that you can select as many slots that you want, and the summary shows how many you selected then you can go to next step.
Here is my code if you guys have some ideas !
function x1(e) {
var target = e.target,
count = +target.dataset.count;
target.style.backgroundColor = count === 1 ? "#707070" : 'black';
target.dataset.count = count === 1 ? 0 : 1;
target.innerHTML = count === 1 ? '' : 'réserver';
target.classList.toggle('Resatxt');
target.classList.toggle('unselectable');
}
Actually this code is for the selection of the slots (it's going background black when you clicl on it, and then back to the normal color when you deselect it).
But i think i can do what i want with this.
I thinked about incrementing +1 when we click on the div but the problem that i dont know how to figure it out is when you deselect it you have to do -1 but im a bit lost.
I tried to be clear but ik that is not really.
If you guys have some ideas, go for it.
Thanks a lot
it's nice to see that your joining the programming community. I hope I understood you correctly and made a simple and minimal example to present you how can you achieve what you want. This is just an idea, don't take it too serious and write your own logic to handle the functionality!
const divs = 98;
const list = document.querySelector("#list");
const selectedList = document.querySelector("#selectedList");
let selected = [];
let elementsAdded = 1;
const onSelectDiv = (element) => {
const elementCopy = element.cloneNode(true);
elementCopy.id += "-copy";
selected = [
...selected,
{
id: elementsAdded,
elementId: element.id
}
];
elementsAdded += 1;
selectedList.appendChild(elementCopy);
};
const onClick = (e) => {
if (e.target.className.includes("selected")) {
e.target.classList.remove("selected");
elementsAdded -= 1;
const elementToDelete = selected.findIndex(
(x) => e.target.id === x.elementId
);
selectedList.removeChild(selectedList.childNodes[elementToDelete + 1]);
selected = selected.filter((x) => x.elementId !== e.target.id);
return;
}
onSelectDiv(e.target);
e.target.className += " selected";
};
for (let i = 0; i < divs; i++) {
const div = document.createElement("div");
div.innerHTML += i;
div.className += "div";
div.id = i;
div.addEventListener("click", function (event) {
onClick(event);
});
list.appendChild(div);
}
.view {
display: flex;
flex-direction: 'column';
}
#list {
display: flex;
width: 400px;
max-width: 500px;
flex-wrap: wrap;
}
.div {
padding: 5px;
background-color: black;
cursor: pointer;
color: white;
border-radius: 10px;
margin: 10px;
}
.selected {
background-color: red;
color: white;
}
<div class="view">
<div>
<p>Elements:</p>
<div id="list">
</div>
</div>
<div>
<p>Selected:</p>
<div id="selectedList">
</div>
</div>
</div>
QUESTION:
Why do I have to click twice to get links in search results menu to load page?
See here:
Type in Staff, or Blog in the filter field. You have to click on each link twice to get the page to load?
https://startech-enterprises.github.io/docs/data-integration-and-etl/branches-and-loops-local.html
I'm trying to get to this behaviour (i/e just one click):
https://learn.microsoft.com/en-us/dotnet/csharp/linq/
NOTE
The code in the link above has now been updated, based on the answers given below
CODE
JS I'm using
/**
* Search Filter
*/
"use strict";
(function searchFilter() {
eventListeners();
// Add Event Listerns
function eventListeners(){
document.getElementById('searchFilter').addEventListener('keyup', searchQuery);
document.getElementById('searchFilter').addEventListener('focusout', searchQuery);
document.getElementById('searchFilter').addEventListener('focusin', searchQuery);
};
function searchQuery(){
// Declare variables
let input, filter, ul_toc, li_toc, ul_suggestions, li_suggestion, a1, a2, a3, i, j, k, txtValue, txtValue2, txtValue3, link;
input = document.getElementById('searchFilter');
filter = input.value.toUpperCase();
ul_toc = document.getElementsByClassName("toc")[0];
li_toc = ul_toc.getElementsByClassName("none");
ul_suggestions = document.getElementsByClassName("searchFilter-suggestions")[0];
// Check whether input is empty. If so hide UL Element
if (filter === "") {
ul_suggestions.classList.add("is-hidden")
};
// Check whether input is not empty. If so show UL Element
if (filter !== "") {
ul_suggestions.classList.remove("is-hidden")
};
// Check whether input is not active. If so hide UL Element
if (input !== document.activeElement) {
setTimeout(function(){
ul_suggestions.classList.add("is-hidden");
}, 2000);
};
// Check whether input is active. If so show UL Element
if (input === document.activeElement) {
ul_suggestions.classList.remove("is-hidden")
};
// Keep emptying UL on each keyup event, or when input element is not active
ul_suggestions.innerHTML = "";
let df = new DocumentFragment();
// Run search query so long as filter is not an empty string
if(filter !== ""){
// Loop through all list items, and update document fragment for those that match the search query
for (i = 0; i < li_toc.length; i++) {
a1 = li_toc[i].getElementsByTagName("a")[0];
txtValue = a1.textContent || a1.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
// Start creating internal HTML
li_suggestion = document.createElement('li');
li_suggestion.classList.add("searchFilter-suggestion");
// Parent span element
let span = document.createElement("SPAN");
span.className = ("is-block is-size-7 has-padding-left-small has-padding-right-small");
link = document.createElement('a');
link.href = a1.href;
span.appendChild(link);
// Child 1 span element
let span2 = document.createElement("SPAN");
span2.className = ("is-block has-overflow-ellipsis-tablet");
span2.textContent = txtValue;
// Child 2 span element
let span3 = document.createElement("SPAN");
span3.className = ("is-block has-text-subtle has-overflow-ellipsis is-size-8 has-line-height-reset has-padding-bottom-extra-small");
j = 0;
let immediateParent = li_toc[i].parentElement;
let correctParent = li_toc[i].parentElement;
// Get top most level of branch --> Set as Node 1
while(true){
if (immediateParent.parentElement.classList.contains('toc')) break;
immediateParent = immediateParent.parentElement;
j++;
};
if (j == 0){
a2 = li_toc[i].getElementsByTagName("a")[0];
}
else {
k = 0;
for ( k = 0; k < j - 1; k++) {
correctParent = correctParent.parentElement;
};
a2 = previousByClass(correctParent, "treeitem");
a2 = child_by_selector(a2, "tree-expander")
}
txtValue2 = a2.textContent || a2.innerText;
txtValue2 = document.createTextNode(txtValue2);
// Insert Chevron Right --> Set as Node 2
let span4 = document.createElement("SPAN");
span4.className = ("has-padding-right-extra-small has-padding-left-extra-small");
span4.innerHTML = ' 〉 ';
span4.setAttribute("style", "font-size: 0.70rem; font-weight: bold");
// Get second-top most level of branch --> Set as Node 3
correctParent = li_toc[i].parentElement;
switch (j) {
case 0:
a3 = "";
break;
case 1:
a3 = li_toc[i].getElementsByTagName("a")[0];
default: {
k = 0;
for ( k = 0; k < j - 2; k++) {
correctParent = correctParent.parentElement;
};
a3 = previousByClass(correctParent, "treeitem");
a3 = child_by_selector(a3, "tree-expander")
}
}
if (a3 != ""){
txtValue3 = a3.textContent || a3.innerText;
txtValue3 = document.createTextNode(txtValue3);
span3.appendChild(txtValue2);
span3.appendChild(span4);
span3.appendChild(txtValue3);
} else {
span3.appendChild(txtValue2);
}
span.firstChild.appendChild(span2);
span.firstChild.appendChild(span3);
li_suggestion.appendChild(span);
df.appendChild(li_suggestion)
}
}
// Output HTML, and remove is-hidden class
ul_suggestions.appendChild(df);
}
}
})();
// WAIT TILL DOCUMENT HAS LOADED BEFORE INITIATING FUNCTIONS
document.addEventListener('DOMContentLoaded', searchFilter);
CSS I'm using:
/* Search Filter */
.filter-icon{
display: inline-block;
height:0.9rem;
width: 1.0rem;
text-transform: none;
text-align: center;
}
.searchFilter {
display: inline-block;
position: relative;
}
.searchFilter-input {
padding-right: 26px;
}
.searchFilter-suggestions {
list-style-type: none;
z-index: 1;
position: absolute;
max-height: 18rem;
min-width: 100%;
max-width: 100%;
padding: 0;
margin: 2px 0 0 !important;
cursor: default;
box-shadow: 0 1.6px 3.6px 0 rgba(0,0,0,0.132),0 .3px .9px 0 rgba(0,0,0,0.108);
border: 1px solid #e3e3e3;
background-color: white;
}
#media screen and (min-width: 768px), print {
.searchFilter-suggestions {
max-width: 500px;
}
}
.searchFilter-suggestion {
display: block;
border: 1px solid transparent;
}
.searchFilter-suggestion a {
color: rgb(23, 23, 22);
text-decoration: none;
}
.searchFilter-suggestion:hover{
background-color: #f2f2f2;;
border: 1px solid rgba(0,0,0,0);
}
.is-hidden {
display: none !important;
}
Relevant portion of HTML with UL that loads the search results:
(The search results document fragment generated by the JS gets loaded in the ul, with the class, searchFilter-suggestions)
form class = "has-margin-bottom-small" action="javascript:" role="search">
<label class="visually-hidden">Search</label>
<div class="searchFilter is-block">
<div class="control has-icons-left">
<input id="searchFilter" class="searchFilter-input input control has-icons-left is-full-width is-small" role="combobox" maxlength="100" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" placeholder="Filter by title" type="text">
<span class="icon is-small is-left">
<img src="/../docs/assets/images/filter.png" class="filter-icon">
</span>
</div>
<ul class="searchFilter-suggestions is-vertically-scrollable is-hidden"></ul>
</div>
</form>
I think the best solution is to remove the focus listeners temporarily.
It should work using this:
(function searchFilter() {
let input = document.getElementById('searchFilter');
let suggestions = document.getElementsByClassName("searchFilter-suggestions")[0];
eventListeners();
// Add Event Listerns
function eventListeners() {
input.addEventListener('keyup', searchQuery);
suggestions.addEventListener("mouseenter", () => removeInputFocusListeners());
suggestions.addEventListener("mouseleave", () => addInputFocusListeners());
};
function addInputFocusListeners() {
input.addEventListener('focusout', searchQuery);
input.addEventListener('focusin', searchQuery);
}
function removeInputFocusListeners() {
input.removeEventListener('focusout', searchQuery);
input.removeEventListener('focusin', searchQuery);
}
...
I am trying to write a script in the head element of my web page where a loop of 10 images will successively increase by 5 pixels each when any one of the looped images is clicked. When the onclick fires, it does create a loop that correctly increases the size for each image, but unfortunately this output gets added to the end of the original loop instead of changing it.
In my head element script, I first tried to use getDocumentById(), then switched to passing the "this" reference to the function, but came up with the same result. I also tried to use addEventListener(), but this didn't work either.
In the head element:
<script>
function growingPumpkins(e) {
for (var i = 0; i < 10; i++) {
e.innerHTML += `<img src='bandit.png' style="width:${50 + i * 5}px; height:${50 + i * 5}px "/>`;
}
}
</script>
In the body element:
<section>
<h2>Growing Pumpkins</h2>
<p id="smashingPumpkins" onclick="growingPumpkins(this)" ></p>
<script>
for (var i = 0; i < 10; i++) {
document.getElementById("smashingPumpkins").innerHTML += "<img src='bandit.png' />";
}
</script>
</section>
I wanted to initially create an HTMLCollection in the head element to achieve the desired result, but wasn't able to get any output when trying that. Right now, I still get the onclick loop concatenated to the original loop.
In order to use Template Literals assertions ${foo} you need backticks, not quotes
`Text ${foo} text`
e.innerHTML += is used to append to the current HTML of an element, not to increase the size of an element.
Instead:No need for for loops. Use document.querySelectorAll() and NodeList.forEach() with Element.addEventListener() to attach a desired function to the Event handler
const grow = ev => {
const el = ev.currentTarget;
el._w = el._w ? el._w + 10 : 40;
el.style.width = `${el._w}px`;
};
document.querySelectorAll(".grow").forEach(el => el.addEventListener('click', grow));
.grow {
display: inline-block;
vertical-align: middle;
width: 30px;
transition: width 0.3s;
cursor: pointer;
}
<img class="grow" src="https://i.imgur.com/GDUJj6t.jpg">
<img class="grow" src="https://i.imgur.com/GDUJj6t.jpg">
Increment size of multiple images on parent click:
const el_backyard = document.querySelector("#backyard");
const tot = 5; // total images
const min_size = 30; // px
const step = 10; // increase by 10 px
// PLANT
for (let i=1; i<=tot; i++) {
const el = new Image();
el.src = "https://i.imgur.com/GDUJj6t.jpg";
el.style.width = `${step * i}px`;
el_backyard.append(el);
}
// GROW
const grow = ev => {
el_backyard.querySelectorAll('img').forEach(img => {
img.style.width = `${img.offsetWidth + step}px`;
});
};
el_backyard.addEventListener('click', grow);
#backyard > * {
display: inline-block;
vertical-align: middle;
transition: width 0.3s;
cursor: pointer;
}
<div id="backyard"></div>
I'm working on a site where I have written some javascript to create a 'typewriter' type effect for the large middle centered text seen below. The script animates the text so that it types forward letter by letter and then deletes the text before starting a new word. The problem I'm having is that when the text is deleted the paragraph element containing the text is empty and the button below 'jumps' to the position where the text was. I'd like to know a good way to fix this issue, either in the javascript or with a simple css positioning fix. I wondered if there was maybe a way to position the button relative to the top "we create digital products text"?
This is my html:
<div class="agency-hero">
<section class="container">
<div class="hero-text customFadeInUp">
<h1 class="tagLine">
We create digital products
</h1>
<p><span class="txt-type " data-wait="2000" data-words='[" "," Websites "," Web Applications "]'></span></p>
<a href="agency-portfolio-4.html" class="stayPut">
See our work
</a>
</div>
</section>
</div>
and the javascript to animate the text:
const TypeWriter = function(txtElement, words, wait = 3000){
this.txtElement = txtElement;
this.words = words;
this.txt='';
this.wordIndex=0;
this.wait=parseInt(wait,10);
this.type();
this.isDeleting = false;
}
// Type Method
TypeWriter.prototype.type = function() {
//current index of word
const current = this.wordIndex % this.words.length;
//get Full text
const fullTxt = this.words[current];
//check for if currently in the deleting state or not
if(this.isDeleting){
this.txt = fullTxt.substring(0,this.txt.length -1);
}else{
//add a character
this.txt = fullTxt.substring(0,this.txt.length +1);
}
//insert txt into element
this.txtElement.innerHTML = `<span class="txt">${this.txt}</span>`;
// Initial Type Speed
let typeSpeed = 300;
if(this.isDeleting){
typeSpeed /= 2;
}
// If word is complete then move on to next word
if(!this.isDeleting && this.txt == fullTxt){
//make pause at the end
typeSpeed = this.wait;
//set Delete to True
this.isDeleting = true;
} else if(this.isDeleting && this.txt == ''){
this.isDeleting=false;
//move to next word
this.wordIndex ++;
// Pause before start typing
typeSpeed = 500;
}
setTimeout(() => this.type(),typeSpeed);
}
// Init on DOM Load
document.addEventListener('DOMContentLoaded',init);
//Init App
function init(){
const txtElement = document.querySelector('.txt-type');
const words = JSON.parse(txtElement.getAttribute('data-words'));
const wait = txtElement.getAttribute('data-wait');
new TypeWriter(txtElement, words, wait);
}
You can use CSS property min-height to maintain the desired gap between both texts. Take a look at the below codes.
With Text-
body {
background-color: lightblue;
}
h1 {
color:black;
text-align: center;
}
p {
font-family: verdana;
font-size: 40px;
background-color:red;
min-height:20px;
}
p+p {
font-size: 20px;
background-color:orange;
}
<h1>We create Digital Products</h1>
<p>Type Writer</p>
<p>See my work</p>
Without Text
body {
background-color: lightblue;
}
h1 {
color:black;
text-align: center;
}
p {
font-family: verdana;
font-size: 40px;
background-color:red;
min-height:20px;
}
p+p {
font-size: 20px;
background-color:orange;
}
<h1>We create Digital Products</h1>
<p></p>
<p>See my work</p>
p is a block element and its height is getting calculated on the basis of its content. Hope it helps.
If you're asking for relative positioning you use the css position property with a value of relative and use the top and left properties to change it. However, you could also use transform to change the positions.
For example:
button {
position:relative;
top:50vh;
}
//Or
button {
transform: translate(0, 50vh);
}
you can change these depending on how you want it.
In my opinion, if I read it correctly, it looks like you want to keep it there so I'd use absolute positioning.
Such as:
button {
position:absolute;
left:50%;
top:90vh;
//This won't move no matter what
}
You could give your <p> a defined height:
.hero-text p {
height: 20px;
}
const TypeWriter = function(txtElement, words, wait = 3000){
this.txtElement = txtElement;
this.words = words;
this.txt='';
this.wordIndex=0;
this.wait=parseInt(wait,10);
this.type();
this.isDeleting = false;
}
// Type Method
TypeWriter.prototype.type = function() {
//current index of word
const current = this.wordIndex % this.words.length;
//get Full text
const fullTxt = this.words[current];
//check for if currently in the deleting state or not
if(this.isDeleting){
this.txt = fullTxt.substring(0,this.txt.length -1);
}else{
//add a character
this.txt = fullTxt.substring(0,this.txt.length +1);
}
//insert txt into element
this.txtElement.innerHTML = `<span class="txt">${this.txt}</span>`;
// Initial Type Speed
let typeSpeed = 300;
if(this.isDeleting){
typeSpeed /= 2;
}
// If word is complete then move on to next word
if(!this.isDeleting && this.txt == fullTxt){
//make pause at the end
typeSpeed = this.wait;
//set Delete to True
this.isDeleting = true;
} else if(this.isDeleting && this.txt == ''){
this.isDeleting=false;
//move to next word
this.wordIndex ++;
// Pause before start typing
typeSpeed = 500;
}
setTimeout(() => this.type(),typeSpeed);
}
// Init on DOM Load
document.addEventListener('DOMContentLoaded',init);
//Init App
function init(){
const txtElement = document.querySelector('.txt-type');
const words = JSON.parse(txtElement.getAttribute('data-words'));
const wait = txtElement.getAttribute('data-wait');
new TypeWriter(txtElement, words, wait);
}
.hero-text p {
height: 20px;
}
<div class="agency-hero">
<section class="container">
<div class="hero-text customFadeInUp">
<h1 class="tagLine">
We create digital products
</h1>
<p><span class="txt-type " data-wait="2000" data-words='[" "," Websites "," Web Applications "]'></span></p>
<a href="agency-portfolio-4.html" class="stayPut">
See our work
</a>
</div>
</section>
</div>
I created a sliding puzzle with different formats like: 3x3, 3x4, 4x3 and 4x4. When you run my code you can see on the right side a selection box where you can choose the 4 formats. The slidingpuzzle is almost done. But I need a function which checks after every move if the puzzle is solved and if that is the case it should give out a line like "Congrantulations you solved it!" or "You won!". Any idea how to make that work?
In the javascript code you can see the first function loadFunc() is to replace every piece with the blank one and the functions after that are to select a format and change the format into it. The function Shiftpuzzlepieces makes it so that you can move each piece into the blank space. Function shuffle randomizes every pieces position. If you have any more question or understanding issues just feel free to ask in the comments. Many thanks in advance.
Since I don't have enough reputation I will post a link to the images here: http://imgur.com/a/2nMlt . These images are just placeholders right now.
Here is the jsfiddle:
http://jsfiddle.net/Cuttingtheaces/vkyxgwo6/19/
As always, there is a "hacky", easy way to do this, and then there is more elegant but one that requires significant changes to your code.
Hacky way
To accomplish this as fast and dirty as possible, I would go with parsing id-s of pieces to check if they are in correct order, because they have this handy pattern "position" + it's expected index or "blank":
function isFinished() {
var puzzleEl = document.getElementById('slidingpuzzleContainer').children[0];
// convert a live list of child elements into regular array
var pieces = [].slice.call(puzzleEl.children);
return pieces
.map(function (piece) {
return piece.id.substr(8); // strip "position" prefix
})
.every(function (id, index, arr) {
if (arr.length - 1 == index) {
// last peace, check if it's blank
return id == "blank";
}
// check that every piece has an index that matches its expected position
return index == parseInt(id);
});
}
Now we need to check it somewhere, and naturally the best place would be after each move, so shiftPuzzlepieces() should be updated to call isFinished() function, and show the finishing message if it returns true:
function shiftPuzzlepieces(el) {
// ...
if (isFinished()) {
alert("You won!");
}
}
And voilĂ : live version.
How would I implement this game
For me, the proper way of implementing this would be to track current positions of pieces in some data structure and check it in similar way, but without traversing DOM or checking node's id-s. Also, it would allow to implement something like React.js application: onclick handler would mutate current game's state and then just render it into the DOM.
Here how I would implement the game:
/**
* Provides an initial state of the game
* with default size 4x4
*/
function initialState() {
return {
x: 4,
y: 4,
started: false,
finished: false
};
}
/**
* Inits a game
*/
function initGame() {
var gameContainer = document.querySelector("#slidingpuzzleContainer");
var gameState = initialState();
initFormatControl(gameContainer, gameState);
initGameControls(gameContainer, gameState);
// kick-off rendering
render(gameContainer, gameState);
}
/**
* Handles clicks on the container element
*/
function initGameControls(gameContainer, gameState) {
gameContainer.addEventListener("click", function hanldeClick(event) {
if (!gameState.started || gameState.finished) {
// game didn't started yet or already finished, ignore clicks
return;
}
if (event.target.className.indexOf("piece") == -1) {
// click somewhere not on the piece (like, margins between them)
return;
}
// try to move piece somewhere
movePiece(gameState, parseInt(event.target.dataset.index));
// check if we're done here
checkFinish(gameState);
// render the state of game
render(gameContainer, gameState);
event.stopPropagation();
return false;
});
}
/**
* Checks whether game is finished
*/
function checkFinish(gameState) {
gameState.finished = gameState.pieces.every(function(id, index, arr) {
if (arr.length - 1 == index) {
// last peace, check if it's blank
return id == "blank";
}
// check that every piece has an index that matches its expected position
return index == id;
});
}
/**
* Moves target piece around if there's blank somewhere near it
*/
function movePiece(gameState, targetIndex) {
if (isBlank(targetIndex)) {
// ignore clicks on the "blank" piece
return;
}
var blankPiece = findBlankAround();
if (blankPiece == null) {
// nowhere to go :(
return;
}
swap(targetIndex, blankPiece);
function findBlankAround() {
var up = targetIndex - gameState.x;
if (targetIndex >= gameState.x && isBlank(up)) {
return up;
}
var down = targetIndex + gameState.x;
if (targetIndex < ((gameState.y - 1) * gameState.x) && isBlank(down)) {
return down;
}
var left = targetIndex - 1;
if ((targetIndex % gameState.x) > 0 && isBlank(left)) {
return left;
}
var right = targetIndex + 1;
if ((targetIndex % gameState.x) < (gameState.x - 1) && isBlank(right)) {
return right;
}
}
function isBlank(index) {
return gameState.pieces[index] == "blank";
}
function swap(i1, i2) {
var t = gameState.pieces[i1];
gameState.pieces[i1] = gameState.pieces[i2];
gameState.pieces[i2] = t;
}
}
/**
* Handles form for selecting and starting the game
*/
function initFormatControl(gameContainer, state) {
var formatContainer = document.querySelector("#formatContainer");
var formatSelect = formatContainer.querySelector("select");
var formatApply = formatContainer.querySelector("button");
formatSelect.addEventListener("change", function(event) {
formatApply.disabled = false;
});
formatContainer.addEventListener("submit", function(event) {
var rawValue = event.target.format.value;
var value = rawValue.split("x");
// update state
state.x = parseInt(value[0], 10);
state.y = parseInt(value[1], 10);
state.started = true;
state.pieces = generatePuzzle(state.x * state.y);
// render game
render(gameContainer, state);
event.preventDefault();
return false;
});
}
/**
* Renders game's state into container element
*/
function render(container, state) {
var numberOfPieces = state.x * state.y;
updateClass(container, state.x, state.y);
clear(container);
var containerHTML = "";
if (!state.started) {
for (var i = 0; i < numberOfPieces; i++) {
containerHTML += renderPiece("", i) + "\n";
}
} else if (state.finished) {
containerHTML = "<div class='congratulation'><h2 >You won!</h2><p>Press 'Play!' to start again.</p></div>";
} else {
containerHTML = state.pieces.map(renderPiece).join("\n");
}
container.innerHTML = containerHTML;
function renderPiece(id, index) {
return "<div class='piece' data-index='" + index + "'>" + id + "</div>";
}
function updateClass(container, x, y) {
container.className = "slidingpuzzleContainer" + x + "x" + y;
}
function clear(container) {
container.innerHTML = "";
}
}
/**
* Generates a shuffled array of id-s ready to be rendered
*/
function generatePuzzle(n) {
var pieces = ["blank"];
for (var i = 0; i < n - 1; i++) {
pieces.push(i);
}
return shuffleArray(pieces);
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
}
body {
font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Helvetica, Arial, sans-serif;
font-size: 12px;
color: #000;
}
#formatContainer {
position: absolute;
top: 50px;
left: 500px;
}
#formatContainer label {
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
}
#formatContainer select {
display: block;
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
}
#formatContainer button {
display: inline-block;
width: 100%;
}
.piece {
width: 96px;
height: 96px;
margin: 1px;
float: left;
border: 1px solid black;
}
.slidingpuzzleContainer3x3,
.slidingpuzzleContainer3x4,
.slidingpuzzleContainer4x3,
.slidingpuzzleContainer4x4 {
position: absolute;
top: 50px;
left: 50px;
border: 10px solid black;
}
.slidingpuzzleContainer3x3 {
width: 300px;
height: 300px;
}
.slidingpuzzleContainer3x4 {
width: 300px;
height: 400px;
}
.slidingpuzzleContainer4x3 {
width: 400px;
height: 300px;
}
.slidingpuzzleContainer4x4 {
width: 400px;
height: 400px;
}
.congratulation {
margin: 10px;
}
}
<body onload="initGame();">
<div id="slidingpuzzleContainer"></div>
<form id="formatContainer">
<label for="format">select format:</label>
<select name="format" id="format" size="1">
<option value="" selected="true" disabled="true"></option>
<option value="3x3">Format 3 x 3</option>
<option value="3x4">Format 3 x 4</option>
<option value="4x3">Format 4 x 3</option>
<option value="4x4">Format 4 x 4</option>
</select>
<button type="submit" disabled="true">Play!</button>
</form>
</body>
Here we have the initGame() function that starts everything. When called it will create an initial state of the game (we have default size and state properties to care about there), add listeners on the controls and call render() function with the current state.
initGameControls() sets up a listener for clicks on the field that will 1) call movePiece() which will try to move clicked piece on the blank spot if the former is somewhere around, 2) check if after move game is finished with checkFinish(), 3) call render() with updated state.
Now render() is a pretty simple function: it just gets the state and updates the DOM on the page accordingly.
Utility function initFormatControl() handles clicks and updates on the form for field size selection, and when the 'Play!' button is pressed will generate initial order of the pieces on the field and call render() with new state.
The main benefit of this approach is that almost all functions are decoupled from one another: you can tweak logic for finding blank space around target piece, to allow, for example, to swap pieces with adjacent ids, and even then functions for rendering, initialization and click handling will stay the same.
$(document).on('click','.puzzlepiece', function(){
var count = 0;
var imgarray = [];
var test =[0,1,2,3,4,5,6,7,8,'blank']
$('#slidingpuzzleContainer img').each(function(i){
var imgalt = $(this).attr('alt');
imgarray[i] = imgalt;
count++;
});
var is_same = (imgarray.length == test.length) && imgarray.every(function(element, index) {
return element === array2[index];
});
console.log(is_same); ///it will true if two array is same
});
try this... this is for only 3*3.. you pass the parameter and makethe array value as dynamically..