I understand that Chrome has built in CTRL Z and Y for Undo and Redo function. But is it possible for the Undo/Redo button to work on textarea using plain JavaScript?
I found the code here (answered by Neil) helpful but it needs jQuery:
How do I make undo/redo in <textarea> to react on 1 word at a time, word by word or character by character?
Here is a StateMaker I made. It is used for undo, redo, and save. It's quite simple. It works fine.
The issue is that e.ctrlKey && e.key === 'z' and the like have strange behavior, when e.preventDefault() in Firefox, so I removed that part. The code below is admittedly imperfect, in that it writes over the last state if the words and states are the same length. But, if I didn't do that, it would have saved a state with every character, which is also doable. Another solution would be to save states based on time. I did not go that route for this example.
//<![CDATA[
/* external.js */
/* StateMaker created by Jason Raymond Buckley */
var doc, bod, I, StateMaker; // for use on other loads
addEventListener('load', function(){
doc = document; bod = doc.body;
I = function(id){
return doc.getElementById(id);
}
StateMaker = function(initialState){
var o = initialState;
if(o){
this.initialState = o; this.states = [o];
}
else{
this.states = [];
}
this.savedStates = []; this.canUndo = this.canRedo = false; this.undoneStates = [];
this.addState = function(state){
this.states.push(state); this.undoneStates = []; this.canUndo = true; this.canRedo = false;
return this;
}
this.undo = function(){
var sl = this.states.length;
if(this.initialState){
if(sl > 1){
this.undoneStates.push(this.states.pop()); this.canRedo = true;
if(this.states.length < 2){
this.canUndo = false;
}
}
else{
this.canUndo = false;
}
}
else if(sl > 0){
this.undoneStates.push(this.states.pop()); this.canRedo = true;
}
else{
this.canUndo = false;
}
return this;
}
this.redo = function(){
if(this.undoneStates.length > 0){
this.states.push(this.undoneStates.pop()); this.canUndo = true;
if(this.undoneStates.length < 1){
this.canRedo = false;
}
}
else{
this.canRedo = false;
}
return this;
}
this.save = function(){
this.savedStates = this.states.slice();
return this;
}
this.isSavedState = function(){ // test to see if current state in use is a saved state
if(JSON.stringify(this.states) !== JSON.stringify(this.savedStates)){
return false;
}
return true;
}
}
var text = I('text'), val, wordCount = 0, words = 0, stateMaker = new StateMaker, save = I('save');
text.onkeyup = function(e){
save.className = undefined; val = this.value.trim(); wordCount = val.split(/\s+/).length;
if(wordCount === words && stateMaker.states.length){
stateMaker.states[stateMaker.states.length-1] = val;
}
else{
stateMaker.addState(val); words = wordCount;
}
}
I('undo').onclick = function(){
stateMaker.undo(); val = text.value = (stateMaker.states[stateMaker.states.length-1] || '').trim();
text.focus();
save.className = stateMaker.isSavedState() ? 'saved' : undefined;
}
I('redo').onclick = function(){
stateMaker.redo(); val = text.value = (stateMaker.states[stateMaker.states.length-1] || '').trim();
text.focus();
save.className = stateMaker.isSavedState() ? 'saved' : undefined;
}
save.onclick = function(){
stateMaker.save(); text.focus(); this.className = 'saved';
}
}); // end load
//]]>
/* external.css */
*{
padding:0; margin:0; border:0; box-sizing:border-box;
}
html,body{
width:100%; height:100%; background:#aaa; color:#000;
}
input{
font:22px Tahoma, Geneva, sans-serif; padding:3px;
}
#text{
width:calc(100% - 20px); height:calc(100% - 70px); font:22px Tahoma, Geneva, sans-serif; padding:3px 5px; margin:10px;
}
#undoRedoSave{
text-align:right;
}
input[type=button]{
padding:0 7px; border-radius:5px; margin-right:10px; border:2px solid #ccc;
}
input[type=button].saved{
border:2px solid #700;
}
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>
<head>
<meta charset='UTF-8' /><meta name='viewport' content='width=device-width, height=device-height, initial-scale:1' />
<title>Test Template</title>
<link type='text/css' rel='stylesheet' href='external.css' />
<script type='text/javascript' src='external.js'></script>
</head>
<body>
<textarea id='text'></textarea>
<div id='undoRedoSave'>
<input id='undo' type='button' value='undo' />
<input id='redo' type='button' value='redo' />
<input id='save' type='button' value='save' />
</div>
</body>
</html>
Related
I created or tried to make a Photo Gallery on one of my web pages. It worked perfectly at first, but when the "Show more images" button is clicked, it changes the logo and doesn't show the other added images. (See pictures below). How can I avoid the logo changing and show the rest of the photos?
I really will appreciate if somebody can help me to find the error. Thanks!
Here's the html code:
<h1 class="headings1">ABOUT US</h1>
<article>
<div id="leftarrow">
<p><</p>
</div>
<figure id="fig2">
<img class="about" width="360" height="202" />
</figure>
<figure id="fig3">
<img class="about" width="480" height="270" />
</figure>
<figure id="fig4">
<img class="about" width="360" height="202" />
</figure>
<div id="rightarrow">
<p>></p>
</div>
<div id="fiveButton">
<p>Show more images</p>
</div>
</article>
Here's javascript code:
"use strict"; // interpret document contents in JavaScript strict mode
/* global variables */
var photoOrder = [1,2,3,4,5];
var figureCount = 3;
/* add src values to img elements based on order specified on photoOrder array */
function populateFigures() {
var filename;
var currentFig;
if (figureCount === 3){
for (var i = 1; i < 4; i++) {
filename = "images/about_0" + photoOrder[i] + "sm.jpg";
currentFig = document.getElementsByClassName("about") [i - 1];
currentFig.src = filename;
}
} else {
for (var i = 0; i < 5; i++) {
filename = "image/about_0" + photoOrder[i] + "sm.jpg";
currentFig = document.getElementsByClassName("about")[1];
currentFig.src = filename;
}
}
}
/* shift all images one figure to the left, and change values in photoOrder array to match */
function rightArrow() {
for (var i = 0; i < 5; i++) {
if ((photoOrder[i] + 1) === 6) {
photoOrder[i] = 1;
} else {
photoOrder[i] += 1;
}
populateFigures();
}
}
/* shift all images one figure to the right, and change values in photoOrder array to match */
function leftArrow() {
for (var i = 0; i < 5; i++) {
if ((photoOrder[i] - 1) === 0) {
photoOrder[i] = 5;
} else {
photoOrder[i] -= 1;
}
populateFigures();
}
}
/* switch to 5-images */
function previewFive() {
//create figure and img elements for fifth image
var articleEl = document.getElementsByTagName("article")[0];
var lastFigure = document.createElement("figure");
lastFigure.id = "fig5";
lastFigure.style.zIndex = "5";
lastFigure.style.position = "absolute";
lastFigure.style.right = "45px"
lastFigure.style.top = "67px";
var lastImage = document.createElement("img");
lastImage.classList = "about";
lastImage.width = "240";
lastImage.height = "135"
lastFigure.appendChild(lastImage);
// articleEl.appendChild(lastFigure);
articleEl.insertBefore(lastFigure, document.getElementById("rightarrow"));
//clone figure element for fifth image and edit to be first image
var firstFigure = lastFigure.cloneNode(true);
firstFigure.id = "fig1";
firstFigure.style.right = "";
firstFigure.style.left = "45px";
// articleEl.appendChild(firstFigure);
articleEl.insertBefore(firstFigure, document.getElementById("fig2"));
//add appropiate src values to two img elements
document.getElementsByTagName("img")[0].src = "images/about_0" + photoOrder[0] + "sm.jpg";
document.getElementsByTagName("img")[4].src = "images/about_0" + photoOrder[4] + "sm.jpg";
figureCount = 5;
//change button to hide extra images
var numberButton = document.querySelector("#fiveButton p");
numberButton.innerHTML = "Show fewer images";
if (numberButton.addEventListener) {
numberButton.removeEventListener("click", previewFive, false);
numberButton.addEventListener("click", previewThree, false);
} else if (numberButton.attachEvent) {
numberButton.detachEvent("onclick", previewFive);
numberButton.attachEvent("onclick", previewThree);
}
}
/* switch to 3-image layout */
function previewThree() {
var articleEl = document.getElementsByTagName("article") [0];
var numberButton = document.querySelector("#fiveButton p");
articleEl.removeChild(document.getElementById("fig1"));
articleEl.removeChild(document.getElementById("fig5"));
figureCount = 3;
numberButton.innerHTML = "Show more images";
if (numberButton.addEventListener) {
numberButton.removeEventListener("click", previewThree, false);
numberButton.addEventListener("click", previewFive, false);
} else if (numberButton.attachEvent) {
numberButton.detachEvent("onclick", previewThree);
numberButton.attachEvent("onclick", previewFive);
}
}
/* Create event listener for left arrow, right arrow and center figure element */
function createEventListeners() {
var leftarrow = document.getElementById("leftarrow");
if (leftarrow.addEventListener) {
leftarrow.addEventListener("click", leftArrow, false);
} else if (leftarrow.attachEvent) {
leftarrow.attachEvent("onclick", leftArrow);
}
var rightarrow = document.getElementById("rightarrow");
if (rightarrow.addEventListener) {
rightarrow.addEventListener("click", rightArrow, false);
}else if (rightarrow.attachEvent) {
rightarrow.attachEvent("onclick", rightArrow);
}
var mainFig = document.getElementsByTagName("img")[1];
if (mainFig.addEventListener) {
mainFig.addEventListener("click", zoomFig, false);
} else if (mainFig.attachEvent) {
mainFig.attachEvent("onclick", zoomFig);
}
var showAllButton = document.querySelector("#fiveButton p");
if (showAllButton.addEventListener) {
showAllButton.addEventListener("click", previewFive, false);
}else if (showAllButton.attachEvent) {
showAllButton.attachEvent("onclick", previewFive);
}
}
/* open center figure in separate window */
function zoomFig() {
var propertyWidth = 960;
var propertyHeight = 600;
var winLeft = ((screen.width - propertyWidth) / 2);
var winTop = ((screen.height - propertyHeight) / 2);
var winOptions = "width = 960, height = 600";
winOptions += ",left=" + winLeft;
winOptions += ",top=" + winTop;
var zoomWindow = window.open("zoom.html", "zoomwin", winOptions);
zoomWindow.focus();
}
/* create event listeners and populate image elements */
function setUpPage() {
createEventListeners();
populateFigures();
}
/* run setUpPage() function when page finishes loading */
if (window.addEventListener) {
window.addEventListener("load", setUpPage, false);
} else if (window.attachEvent) {
window.attachEvent("onload", setUpPage);
}
I have included a tiny library with a constructor called ImgViewer. Admittedly, if you resize the screen vertically, it can be a slight issue, but it's all the time I'm willing to take right now. Hopefully you can learn something from this.
//<![CDATA[
/* js/external.js */
let doc, htm, bod, nav, M, I, mobile, S, Q, hC, aC, rC, tC, ImgViewer; // for use on other loads
addEventListener('load', ()=>{
doc = document; htm = doc.documentElement; bod = doc.body; nav = navigator; M = tag=>doc.createElement(tag); I = id=>doc.getElementById(id); mobile = /Mobi/i.test(nav.userAgent);
S = (selector, within)=>{
let w = within || doc;
return w.querySelector(selector);
}
Q = (selector, within)=>{
let w = within || doc;
return w.querySelectorAll(selector);
}
hC = (node, className)=>{
return node.classList.contains(className);
}
aC = (node, ...classNames)=>{
node.classList.add(...classNames);
return aC;
}
rC = (node, ...classNames)=>{
node.classList.remove(...classNames);
return rC;
}
tC = (node, className)=>{
node.classList.toggle(className);
return tC;
}
ImgViewer = function(imgArray, imgsWide = 3){
if(imgsWide % 2 === 0){
throw new Error('imgsWide must be odd number');
}
this.container = M('div'); this.leftArrow = M('div'); this.view = M('div');
this.rightArrow = M('div'); this.container.className = 'imgViewer';
this.view.className = 'view';
this.leftArrow.className = this.rightArrow.className = 'arrow';
this.leftArrow.textContent = '<'; this.rightArrow.textContent = '>';
this.container.appendChild(this.leftArrow); this.container.appendChild(this.view); this.container.appendChild(this.rightArrow);
const center = Math.floor(imgsWide/2), iA = [], imA = imgArray.slice();
this.size = ()=>{
const b = this.view.getBoundingClientRect(), w = b.width/imgsWide-40;
for(let i=0,l=iA.length; i<l; i++){
iA[i].width = i === center ? w+50 : w;
}
return this;
}
this.create = (where = bod)=>{ // default document.body
where.appendChild(this.container);
const v = this.view, b = v.getBoundingClientRect(), w = b.width/imgsWide-40;
for(let i=0,m,l=imgArray.length; i<l; i++){
m = M('img');
m.width = i === center ? w+50 : w;
m.src = imgArray[i]; iA.push(m); // cache all the images
if(i < imgsWide){
if(i === center){
// add a click event to center if you want - I did not
}
else if(i < center){
m.onclick = ()=>{
for(let n=i; n<center; n++){
this.rightArrow.onclick();
}
}
}
else{
m.onclick = ()=>{
for(let n=i; n<imgsWide; n++){
this.leftArrow.onclick();
}
}
}
v.appendChild(m);
}
}
const c = v.children;
const dispImgs = ()=>{
for(let n=0; n<imgsWide; n++){
c[n].src = imA[n];
}
}
this.leftArrow.onclick = ()=>{
imA.push(imA.shift()); dispImgs();
}
this.rightArrow.onclick = ()=>{
imA.unshift(imA.pop()); dispImgs();
}
onresize = this.size;
return this;
}
}
// tiny library above - magic below can be put on another page using a load Event except `}); // end load` line
const imgURLs = [
'https://images.freeimages.com/images/large-previews/afa/black-jaguar-1402097.jpg',
'https://images.freeimages.com/images/large-previews/7e9/ladybird-1367182.jpg',
'https://images.freeimages.com/images/large-previews/535/natural-wonders-1400924.jpg',
'https://images.freeimages.com/images/large-previews/035/young-golden-retriever-1404848.jpg',
'https://images.freeimages.com/images/large-previews/981/cow-1380252.jpg',
'https://images.freeimages.com/images/large-previews/9fc/yet-another-flower-1399208.jpg',
'https://images.freeimages.com/images/large-previews/72c/fox-1522156.jpg',
'https://images.freeimages.com/images/large-previews/e2a/boise-downtown-1387405.jpg',
'https://images.freeimages.com/images/large-previews/f37/cloudy-scotland-1392088.jpg'
];
const imgV = new ImgViewer(imgURLs);
imgV.create();
}); // end load
//]]>
/* css/external.css */
*{ /* size font individually to avoid font whitespace */
box-sizing:border-box; font-size:0; margin:0; padding:0; overflow:hidden;
}
html,body{
width:100%; height:100%;
} /* below is what you need to see - above is set for this example */
.imgViewer{
width:100%; height:100%;
}
.imgViewer,.imgViewer *{
display:flex; justify-content:center; align-items:center;
}
.imgViewer>.arrow{
cursor:pointer; width:32px; height:70px; background:#777; color:#fff; font:bold 14px san-serif;
}
.imgViewer>.view{
width:calc(100% - 32px); height:100%;
}
.imgViewer>.view>img{
cursor:pointer; margin:0 7px; box-shadow:1px 1px 2px;
}
.imgViewer>.view>img:first-child{
margin-left:0;
}
.imgViewer>.view>img:last-child{
margin-right:0;
}
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' /><meta name='viewport' content='width=device-width, height=device-height, initial-scale:1, user-scalable=no' />
<title>ImgViewer</title>
<link type='text/css' rel='stylesheet' href='css/external.css' />
<script src='js/external.js'></script>
</head>
<body>
</body>
</html>
Of course, I didn't make the dummy window for zooming, but you can ask another question for that!
I have a function that gets me the coordinates of the position of an image when you click on it. What I want to achieve is that when I click on the image draw a point in that same position as shown in the image:
this is my code:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<img src="https://t1.up.ltmcdn.com/es/images/2/6/6/img_organos_internos_del_cuerpo_humano_lista_completa_1662_600.jpg" height="500" width="500" alt="dragon">
<script>
$(document).ready(function() {
$("img").on("click", function(event) {
var x = event.pageX - this.offsetLeft;
var y = event.pageY - this.offsetTop;
alert("X Coordinate: " + x + " Y Coordinate: " + y);
});
});
</script>
Just append an absolutely positioned element to the coordinates:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<img src="https://t1.up.ltmcdn.com/es/images/2/6/6/img_organos_internos_del_cuerpo_humano_lista_completa_1662_600.jpg" height="500" width="500" alt="dragon">
<script>
$(document).ready(function() {
const size = 4;
$("img").on("click", function(event) {
var x = event.pageX - this.offsetLeft;
var y = event.pageY - this.offsetTop;
$(this).parent().append(`<div style="width: ${size}px; height: ${size}px; background: black; position: absolute; top: ${y + size}px; left: ${x + size}px; border-radius: ${size}px"/>`);
});
});
</script>
I would use raw JavaScript. I don't have time to fully explain right now, but may later. Good Luck!
//<![CDATA[
/* js/external.js */
let get, post, doc, htm, bod, nav, M, I, mobile, S, Q, aC, rC, tC; // for use on other loads
addEventListener('load', ()=>{
get = (url, success, context)=>{
const x = new XMLHttpRequest;
const c = context || x;
x.open('GET', url);
x.onload = ()=>{
if(success)success.call(c, JSON.parse(x.responseText));
}
x.send();
}
post = function(url, send, success, context){
const x = new XMLHttpRequest;
const c = context || x;
x.open('POST', url);
x.onload = ()=>{
if(success)success.call(c, JSON.parse(x.responseText));
}
if(typeof send === 'object' && send && !(send instanceof Array)){
if(send instanceof FormData){
x.send(send);
}
else{
const fd = new FormData;
for(let k in send){
fd.append(k, JSON.stringify(send[k]));
}
x.send(fd);
}
}
else{
throw new Error('send argument must be an Object');
}
return x;
}
doc = document; htm = doc.documentElement; bod = doc.body; nav = navigator; M = tag=>doc.createElement(tag); I = id=>doc.getElementById(id);
mobile = nav.userAgent.match(/Mobi/i) ? true : false;
S = (selector, within)=>{
var w = within || doc;
return w.querySelector(selector);
}
Q = (selector, within)=>{
var w = within || doc;
return w.querySelectorAll(selector);
}
aC = function(){
const a = [].slice.call(arguments), n = a.shift();
n.classList.add(...a);
return aC;
}
rC = function(){
const a = [].slice.call(arguments), n = a.shift();
n.classList.remove(...a);
return rC;
}
tC = function(){
const a = [].slice.call(arguments), n = a.shift();
n.classList.toggle(...a);
return tC;
}
// magic under here
const stage = I('stage'), human = I('human'), dot = M('div'), dotStyle = dot.style;
dot.id = 'dot';
let move = false;
function moveDot(e){
if(move === true){
let left = e.clientX, top = e.clientY;
if(!stage.contains(dot))stage.appendChild(dot);
dotStyle.left = left+'px'; dotStyle.top = top+'px';
}
}
if(mobile){
human.ontouchstart = (e)=>{
move = true; moveDot(e);
}
human.ontouchmove = moveDot;
human.ontouchend = ()=>{
move = false;
}
}
else{
human.onmousedown = e=>{
move = true; e.preventDefault(); moveDot(e);
}
human.onmousemove = moveDot;
human.onmouseup = ()=>{
move = false;
}
}
}); // end load
/* css/external.css */
*{
box-sizing:border-box;
}
html,body{
width:100%; height:100%; padding:0; margin:0; background:#ccc;
}
.main,#stage{
width:100%; height:100%;
}
#stage{
position:relative; height:100%;
}
#stage>*{
position:absolute;
}
#human{
height:100%; margin:auto; top:0; right:0; bottom;0; left:0;
}
#dot{
border:1px solid #009; border-radius:50%;
}
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>
<head>
<meta charset='UTF-8' /><meta name='viewport' content='width=device-width, height=device-height, initial-scale:1, user-scalable=no' />
<title>Title Here</title>
<link type='text/css' rel='stylesheet' href='css/external.css' />
<script src='js/external.js'></script>
</head>
<body>
<div class='main'>
<div id='stage'>
<img id='human' src='https://t1.up.ltmcdn.com/es/images/2/6/6/img_organos_internos_del_cuerpo_humano_lista_completa_1662_600.jpg' alt='dragon' />
</div>
</div>
</body>
</html>
UPDATE:THIS WORKS ON CHROME, HOW CAN I GET THIS TO WORK ON FIREFOX
I am trying to fade consecutive background images when I hover over a specific element. For example, if I have three text elements, and if I hover over one, I would like the background image to change with a transitional effect.
Here is my codepen: https://codepen.io/tmocodes/pen/MWwXxZO?editors=0110
Here is my inspiration source: https://www.awwwards.com/inspiration/fullscreen-menu-66nord
let seattleEl = document.querySelector("#Seattle");
let ecuadorEl = document.querySelector("#Ecuador");
let arizonaEl = document.querySelector("#Arizona");
let imgArea = document.querySelector(".container");
ecuadorEl.addEventListener("mouseover", e => {
imgArea.style.background = "url('https://www.telegraph.co.uk/content/dam/Travel/2019/November/ecuador.jpg')";
imgArea.classList.add = 'bg'
});
seattleEl.addEventListener("mouseover", e => {
imgArea.style.background =
"url('https://cdn.vox-cdn.com/thumbor/avHeJenMsyJoJ3WBGHl24QWnybk=/0x0:7360x4912/1200x675/filters:focal(3092x1868:4268x3044)/cdn.vox-cdn.com/uploads/chorus_image/image/66498601/shutterstock_482690140.0.jpg')";
imgArea.classList.add = 'bg'
});
*{
padding:0;
margin:0;
box-sizing:border-box
}
.container{
background: url("https://www.telegraph.co.uk/content/dam/Travel/2019/November/ecuador.jpg");
background-size: cover !important;
background-repeat: no-repeat !important;
background-position: center !important;
height:100vh;
transition: all .3s;
h1{
color:white;
opacity: .6;
font-family: Helvetica;
font-size: 48px;
padding: 10px;
cursor:pointer;
display:block;
width:min-content;
height:min-content;
margin-bottom:10px;
&:hover{
opacity:1;
}
}
}
#keyframes transition {
from {
opacity:0
}
to {
opacity:1
}
}
.bg{
background-color: black;
z-index:10;
animation: transition .3s !important;
transition: all .2s;
}
<div class="container">
<h1 id="Ecuador">Ecuador</h1>
<h1 id="Seattle">Seattle</h1>
<h1 id="Arizona">Arizona</h1>
</div>
Maybe this will help?
//<![CDATA[
/* js/external.js */
let get, post, doc, html, bod, nav, M, I, mobile, S, Q, hC, aC, rC, tC, inArray, isNum, isInt; // for use on other loads
addEventListener('load', ()=>{
get = (url, success)=>{
const x = new XMLHttpRequest;
x.open('GET', url);
x.onload = ()=>{
if(success)success(JSON.parse(x.responseText));
}
x.send();
return x;
}
post = (url, send, success)=>{
const x = new XMLHttpRequest;
x.open('POST', url);
x.onload = ()=>{
if(success)success(JSON.parse(x.responseText));
}
if(typeof send === 'object' && send && !(send instanceof Array)){
if(typeof FormData !== 'undefined' && send instanceof FormData){
x.send(send);
}
else{
let s, r = [];
for(let p in send){
s = send[p];
if(typeof s === 'object')s = JSON.stringify(s);
r.push(encodeURIComponent(p)+'='+encodeURIComponent(s));
}
x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); x.send(r.join('&'));
}
}
else{
throw new Error('send argument must be an Object');
}
return x;
}
doc = document; html = doc.documentElement; bod = doc.body; nav = navigator; M = tag=>doc.createElement(tag); I = id=>doc.getElementById(id);
mobile = nav.userAgent.match(/Mobi/i) ? true : false;
S = (selector, within)=>{
const w = within || doc;
return w.querySelector(selector);
}
Q = (selector, within)=>{
const w = within || doc;
return w.querySelectorAll(selector);
}
hC = (element, className)=>{
return element.classList.contains(className);
}
aC = (element, className, func)=>{
element.classList.add(className.trim());
if(func)func(element);
return (className, func)=>{
return aC(element, className, func);
}
}
rC = (element, className, func)=>{
element.classList.remove(className.trim());
if(func)func(element);
return (className, func)=>{
return rC(element, className, func);
}
}
tC = (element, className, yFunc, nFunc)=>{
const c = element.classList, n = className.trim();
if(c.contains(n)){
c.remove(n);
if(nFunc)nFunc(element);
}
else{
c.add(n);
if(yFunc)yFunc(element);
}
return (className, yFunc, nFunc)=>{
return tC(element, className, yFunc, nFunc);
}
}
inArray = (mixed, array)=>{
if(array.indexOf(mixed) === -1){
return false;
}
return true;
}
isNum = mixed=>typeof mixed === 'number' && !isNaN(mixed); isInt = mixed=>Number.isInteger(mixed);
// put below on another page using a load Event - except the end load line and below
function FadeMaker(faderElement, placesElement){
const places = [];
let place;
this.addPlace = (name, background)=>{
const h = M('h1'), d = M('div');
aC(d, 'fadeOut'); d.style.backgroundImage = 'url("'+background+'")';
h.textContent = name;
h.onmouseenter = ()=>{
rC(place, 'fadeIn'); aC(place, 'fadeOut'); rC(d, 'fadeOut'); aC(d, 'fadeIn');
place = d;
}
placesElement.appendChild(h); faderElement.appendChild(d); places.push([h, d]);
return this;
}
this.make = ()=>{
place = places[0][1]; rC(place, 'fadeOut');
places.forEach(a=>{
aC(a[1], 'fade');
});
return this;
}
}
const fm = new FadeMaker(I('fader'), I('places'));
fm.addPlace('Equador', 'https://www.telegraph.co.uk/content/dam/Travel/2019/November/ecuador.jpg').addPlace('Seattle', 'https://cdn.vox-cdn.com/thumbor/avHeJenMsyJoJ3WBGHl24QWnybk=/0x0:7360x4912/1200x675/filters:focal(3092x1868:4268x3044)/cdn.vox-cdn.com/uploads/chorus_image/image/66498601/shutterstock_482690140.0.jpg').addPlace('Arizona', 'https://azgovernor.gov/sites/default/files/styles/panopoly_image_original/public/6871930-arizona.jpg?itok=sbFgZG8r').make();
}); // end load
//]]>
/* css/external.css */
*{
box-sizing:border-box; padding:0; margin:0;
}
html,body,.container,#fader,.fade{
width:100%; height:100%;
}
.container{
position:relative;
}
.container>div,.fade{
position:absolute;
}
.fade{
background-position:center; background-size:cover; transition:opacity 0.5s ease-in-out;
}
.hide{
display:none;
}
#places>h1{
cursor:pointer; color:#fff; font:bold 48px Helvetica; padding:10px 20px 10px;
}
.fadeOut{
opacity:0;
}
.fadeIn{
opacity:1;
}
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>
<head>
<meta charset='UTF-8' /><meta name='viewport' content='width=device-width, height=device-height, initial-scale:1, user-scalable=no' />
<title>Title Here</title>
<link type='text/css' rel='stylesheet' href='css/external.css' />
<script src='js/external.js'></script>
</head>
<body>
<div class='container'>
<div id='fader'></div>
<div id='places'></div>
</div>
</body>
</html>
You can ignore most of the JavaScript above FadeMaker... but use that stuff all the time in the future. Notice that FadeMaker takes two arguments. One where all the fader Elements go and one where the name Elements go. Once you create a new instance, you just fadeMakerInstance.addPlace(yourNameHere, yourURLHere), then .make() when your done. Note that you don't have to chain the methods like I show in the example. Once you have the instance variable you can just execute right off that like fm.addPlace(nameOne, urlOne); fm.addPlace(nameTwo, urlTwo); fm.make().
The problem is that I have created a dynamic navigation...
You click on a a-tag which has a function that displays some buttons.
I have assigned the function to the a-tag with an addEventListener.
It works in all the browsers, but IE...
When I click the tag, buttons are not becoming visible. And no error appears.
P.S.: I'm spanish btw, I'm sorry about my english :3
/* Javascript */
window.onload = function() {
var boton_menu = document.getElementById("boton_menu");
/* Compatibilidad con navegadores web */
if(boton_menu.addEventListener){
boton_menu.addEventListener("click", menu_usuario, false);
} else {
if(boton_menu.attachEvent){
boton_menu.attachEvent("onclick", menu_usuario);
}
}
}
/* Despliega el menĂº del usuario*/
function menu_usuario() {
var boton_menu = document.getElementById("boton_menu");
var perfil = document.getElementById("perfil");
var ajustes = document.getElementById("ajustes");
var desconectar = document.getElementById("desconectar");
if(boton_menu.className == ""){
boton_menu.className = "active";
perfil.className = "active";
ajustes.className = "active";
desconectar.className = "active";
} else {
boton_menu.className = "";
perfil.className = "";
ajustes.className = "";
desconectar.className = "";
}
}
This works in IE:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="">
<meta charset="utf-8">
<meta name="author" content="Antonio Bueno">
<title>Button Problems</title>
<style>
a {background-color: red; color: #FFF; padding: 10px;}
a.active {background-color: blue;}
</style>
<script>
window.onload = function(){
var some_button = document.getElementById("some_button");
if(some_button.addEventListener){
some_button.addEventListener("click", DisplayButton, false);
} else {
if(some_button.attachEvent){
some_button.attachEvent("onclick", DisplayButton);
}
}
}
function DisplayButton(){
var some_button = document.getElementById("some_button");
var another_button = document.getElementById("another_button");
if(some_button.className == ""){
some_button.className = "active";
another_button.style.display = "none";
} else {
some_button.className = "";
another_button.style.display = "inline-block";
}
}
</script>
</head>
<body>
<a id="some_button" class="">Click on me</a>
<input id="another_button" type="submit" value="example" />
</body>
</html>
I'm trying to implement a single player tic tac toe game in which the computer player never loses (forces a draw or wins every time). After searching around it seems using the minimax strategy is pretty standard for this, yet I can't seem to get my algorithm to work correctly, which causes the computer to pick a really bad move. Can anyone help me find where my code has gone wrong?
'O' is the user (max) and 'X' is the computer (min)
EDIT: I re-posted my code, I've fixed some things within getComputerNextMove and a few minor bugs...now my code is finding better scores, but it doesn't always pick up if someone wins (the functions seem to be alright, I think I'm just not checking in the right spots). It also isn't picking the best move. There are some tester functions at the bottom to look at how minimax works/to test game states (winners/draw).
tictactoe.js:
// Setup
var user = new Player('user', 'O', 'green');
var computer = new Player('computer', 'X', 'pink');
window.players = [user, computer];
var gameBoard = new GameBoard();
gameBoard.initializeBoard();
// Trackers for the board and moves
var wins = ['012', '345', '678', '036', '147', '258', '048', '246'];
var moves = 1;
var currentPlayer = players[0];
var game = true;
function Player(name, selector, color) {
this.name = name;
this.moves = [];
this.selector = selector;
this.color = color;
this.makeMove = function(boxId){
if(!gameBoard.isGameOver() && gameBoard.isValidMove(boxId)){
markBox(this, boxId);
this.updateMoves(boxId);
gameBoard.updateBoard(this.selector, boxId);
setTimeout(function () { nextTurn() }, 75);
}
else{
if(gameBoard.isXWinner())
endGame("Computer");
else if(gameBoard.isOWinner())
endGame("You");
else
endGame();
}
}
this.updateMoves = function(boxId){
moves = moves + 1;
this.moves.push(boxId);
}
}
function Square(mark){
this.mark = mark;
this.position;
this.id;
this.score = 0; //for minimax algorithm
}
function GameBoard(){
this.state = [9];
this.initializeBoard = function(){
for(var space = 0; space < 9; space++){
var square = new Square("-");
this.state[space] = square;
this.state[space].position = space;
this.state[space].id = space;
this.state[space].score = 0;
}
}
this.getAvailableSpaces = function(){
var availableSpaces = [];
for(var space = 0; space < this.state.length; space++){
if(this.state[space].mark === "-")
availableSpaces.push(this.state[space]);
}
return availableSpaces;
}
this.updateBoard = function(selector, position){
this.state[position].mark = selector;
}
this.getPositionOfMarks = function(){
var positionOfMarks = [];
for(var sqr=0; sqr<this.state.length; sqr++){
var positionOfMark = {
'position' : this.state[sqr].position,
'mark' : this.state[sqr].mark
}
positionOfMarks.push(positionOfMark);
}
return positionOfMarks;
}
this.getXMovesAsString = function(){
var positionOfMarks = this.getPositionOfMarks();
var xMoves = "";
for(var pm=0; pm < positionOfMarks.length; pm++){
if(positionOfMarks[pm].mark === "X"){
var m = parseInt(positionOfMarks[pm].position);
xMoves += m;
}
}
return xMoves;
}
this.getOMovesAsString = function(){
var positionOfMarks = this.getPositionOfMarks();
var oMoves = "";
for(var pm=0; pm < positionOfMarks.length; pm++){
if(positionOfMarks[pm].mark === "O"){
var m = parseInt(positionOfMarks[pm].position);
oMoves += m;
}
}
return oMoves;
}
this.isValidMove = function(boxId){
return this.state[boxId].mark === "-"
}
this.isOWinner = function(){
var winner = false;
var oMoves = this.getOMovesAsString();
for(var win=0; win<wins.length; win++){
if(oMoves.search(wins[win]) >= 0)
winner = true
}
return winner;
}
this.isXWinner = function(){
var winner = false;
var xMoves = this.getXMovesAsString();
for(var win=0; win<wins.length; win++){
if(xMoves.search(wins[win]) >= 0)
winner = true;
}
return winner;
}
this.isDraw = function(){
var draw = true;
if(!this.isOWinner() && !this.isXWinner()){
for(var space=0; space < this.state.length; space++){
if(this.state[space].mark === "-"){
draw = false;
}
}
}
return draw;
}
this.isGameOver = function(){
var gameOver = false;
if(gameBoard.isDraw() ||
gameBoard.isOWinner() ||
gameBoard.isXWinner())
gameOver = true;
return gameOver;
}
this.printBoardToConsole = function(){
var row1 = [];
var row2 = [];
var row3 = [];
for(var space=0; space<this.state.length; space++){
if(space < 3)
row1.push((this.state[space].mark));
else if(space >= 3 && space < 6)
row2.push((this.state[space].mark));
else
row3.push((this.state[space].mark));
}
console.log(row1);
console.log(row2);
console.log(row3);
}
GameBoard.prototype.clone = function(){
var clone = new GameBoard();
clone.initializeBoard();
for(var square=0; square < this.state.length; square++){
clone.state[square].mark = this.state[square].mark;
clone.state[square].position = this.state[square].position;
clone.state[square].id = this.state[square].id;
clone.state[square].score = this.state[square].score;
}
return clone;
}
}
// Handle clicks
$(".box").click(function (event) {
if (getCurrentPlayer() === user && game == true){
user.makeMove(event.target.id);
}
else overlay("Wait your turn!");
});
// *** MOVES ***
// With every move, these functions update the state of the board
function getComputerNextMove(board){
var availableSquares = board.getAvailableSpaces();
var prevScore = -100000;
var bestMove;
for(var square = 0; square < availableSquares.length; square++){
var gameBoardChild = board.clone();
gameBoardChild.updateBoard("X", availableSquares[square].position);
console.log("gameBoardChild: " + gameBoardChild.printBoardToConsole());
console.log("===================");
var currentScore = min(gameBoardChild);
console.log("Child Score: " + currentScore);
if(currentScore > prevScore){
prevScore = currentScore;
bestMove = availableSquares[square].position;
console.log("Best Move: " + bestMove);
}
}
console.log("====================================");
return bestMove;
}
function max(board){
if(board.isOWinner()) return 1;
else if(board.isXWinner()) return -1;
else if(board.isDraw()) return 0;
var availableSquares = board.getAvailableSpaces();
var bestScore = -100000;
for(var square=0; square<availableSquares.length; square++){
var gameBoardChild = board.clone();
gameBoardChild.updateBoard("O", availableSquares[square].position);
var move = min(gameBoardChild);
if(move > bestScore)
bestScore = move;
}
return bestScore;
}
function min(board){
if(board.isOWinner()) return 1;
else if(board.isXWinner()) return -1;
else if(board.isDraw()) return 0;
var availableSquares = board.getAvailableSpaces();
var bestScore = 100000;
for(var square=0; square<availableSquares.length; square++){
var gameBoardChild = board.clone();
gameBoardChild.updateBoard("X", availableSquares[square].position);
var move = max(gameBoardChild);
if(move < bestScore)
bestScore = move;
}
return bestScore;
}
function markBox(player, boxId) {
$("#" + boxId).text(player.selector).addClass(player.color);
}
// *** SETTERS & GETTERS
function getCurrentPlayer() {
return currentPlayer;
}
function setCurrentPlayer(player) {
currentPlayer = player;
return currentPlayer;
}
function nextTurn() {
if(!gameBoard.isGameOver()){
if (getCurrentPlayer() === user) {
setCurrentPlayer(computer);
computer.makeMove(getComputerNextMove(gameBoard));
}
else setCurrentPlayer(user);
}
else{
if(gameBoard.isOWinner())
endGame("You");
else if(gameBoard.isXWinner())
endGame("Computer");
else
endGame();
}
}
function endGame(winner) {
game = false;
if (winner) {
overlay(winner.name + " wins!");
} else overlay("It's a draw!");
}
//Toggles info box (to avoid using alerts)
function overlay(message) {
$("#info").text(message);
$el = $("#overlay");
$el.toggle();
}
function buildTestBoard(test){
var board = new GameBoard();
board.initializeBoard();
if(test === "minimax"){
board.updateBoard("O", 0);
board.updateBoard("O", 2);
board.updateBoard("O", 4);
board.updateBoard("O", 8);
board.updateBoard("X", 1);
board.updateBoard("X", 6);
board.updateBoard("X", 7);
}
else if(test === "xwin"){
board.updateBoard("X", 6);
board.updateBoard("X", 4);
board.updateBoard("X", 2);
}
board.printBoardToConsole();
return board;
}
function testMinimax(){
var board = buildTestBoard("minimax");
var move = getComputerNextMove(board);
console.log("Best move: " + move);
}
function testWinner(){
var board = buildTestBoard("xwin");
return board.isXWinner();
}
index.html:
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.6.2.min.js"></script>
</head>
<body>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an <strong>outdated</strong> browser. Please
upgrade your browser or
activate Google Chrome Frame
to improve your experience.
</p>
<![endif]-->
<h2 id="title">Single Player Tic Tac Toe</h2>
<div id='0' class='box'></div>
<div id='1' class='box'></div>
<div id='2' class='box'></div>
<br />
<div id='3' class='box'></div>
<div id='4' class='box'></div>
<div id='5' class='box'></div>
<br />
<div id='6' class='box'></div>
<div id='7' class='box'></div>
<div id='8' class='box'></div>
<div id="overlay">
<div>
<p id="info"></p>
</div>
</div>
<!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
<script>
var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']];
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src='//www.google-analytics.com/ga.js';
s.parentNode.insertBefore(g,s)}(document,'script'));
</script>
</body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.9.1.min.js"><\/script>')</script>
<script src="js/plugins.js"></script>
<script src="js/main.js"></script>
<script src="js/game/tictactoe.js"></script>
</html>
css:
.box {
width:100px;
height:100px;
display:inline-block;
background:#eee;
margin:3px 1px;
text-align:center;
font-size:24px;
font-family:arial;
vertical-align:bottom;
line-height:450%
}
.box:hover {
cursor: pointer;
}
a {
text-decoration:none;
}
.label {
position:relative;
bottom:0;
right:0
}
.green {
background:#90EE90;
}
.pink {
background:#FDCBBB;
}
.credit {
color:#333;
font-family:arial;
font-size:13px;
margin-top:40px;
}
#overlay {
position: absolute;
left: 300px;
top: 0px;
width:20%;
height:20%;
text-align:center;
z-index: 1000;
}
#overlay div {
width:100px;
margin: 100px auto;
background-color: #fff;
border:1px solid #000;
padding:15px;
text-align:center;
align: right;
}