js object value change on click in localStorage - javascript

I have object, in that object there is this array:
[
{
"title": "Title1",
"status": false
},
{
"title": "Title2",
"status": false
}
]
and when I press the a tag, I want to change element's status from false to true and move it from "Tasks" section to "Done" section. Like this.
But in changeStatus function I'm getting error:
Uncaught TypeError: Cannot set property 'status' of undefined
at changeStatus (script.js:116)
at HTMLAnchorElement.onclick (?new-task=Task1:1)
How could I change my element's status on the click?
var tasks = {};
var element = {};
var tasksList;
var index;
Date.shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
function short_months(dt) {
return Date.shortMonths[dt.getMonth()];
}
function taskList() {
var today = new Date();
var full_date = short_months(today) + " " + today.getDate() + " " + today.getYear();
element["title"] = document.getElementById('new-task').value;
element["status"] = false;
element["date"] = full_date;
var oldItems = JSON.parse(localStorage.getItem('tasksAll')) || [];
oldItems.push(element);
localStorage.setItem('tasksAll', JSON.stringify(oldItems));
updateData();
}
function updateData() {
var retrievedData = localStorage.getItem("tasksAll");
tasksList = JSON.parse(retrievedData);
var htmlNotDone = "";
var htmlDone = "";
var falseCount = 0;
for (index = 0; index < tasksList.length; index++) {
if (tasksList[index].status === false) {
falseCount++;
htmlNotDone += '<div class="task-element">';
htmlNotDone += '<div class="task-left-element"><p>' + tasksList[index].date + '</p><h4>' + tasksList[index].title + tasksList[index].status + '</h4></div>';
htmlNotDone += '<div class="task-right-element">';
htmlNotDone += '<span class="checkmark"><div class="checkmark_stem"></div><div class="checkmark_kick"></div></span>';
htmlNotDone += '</div>';
htmlNotDone += '</div>';
} else {
htmlDone = htmlDone + tasksList[index].title;
}
}
if (htmlNotDone === "") {
document.getElementById("task-list").innerHTML = "Nothing";
} else {
document.getElementById("task-list").innerHTML = htmlNotDone;
}
if (htmlDone === "") {
document.getElementById("done-task-count").innerHTML = "Nothing";
} else {
document.getElementById("done-task-count").innerHTML = htmlDone;
}
}
function changeStatus(index) {
tasksList[index].status = true;
}
updateData();
#font-face {
font-family: font-Heavy;
src: url(fonts/Aileron-Heavy.otf);
}
#font-face {
font-family: font-Bold;
src: url(fonts/Aileron-Bold.otf);
}
#font-face {
font-family: font-Light;
src: url(fonts/Aileron-Light.otf);
}
#font-face {
font-family: font-Regular;
src: url(fonts/Aileron-Regular.otf);
}
body {
background-color: #e5e5e5;
}
.container {
position: fixed;
left: 40%;
top: 10%;
transform: translate(-40%, -10%);
width: 80%;
background-color: #f7f9fa;
}
.container .content {
margin-left: 25%;
margin-right: 25%;
}
.container .content h1 {
font-family: font-Heavy;
color: #2f80ed;
}
.content form input[type=text] {
font-family: font-Regular;
padding-left: 15px;
min-height: 60px;
width: 100%;
border: 1px solid #e0e0e0;
border-radius: 3px;
box-shadow: inset 0px 0px 10px #e0e0e0;
}
::placeholder {
color: #929292;
}
.content .undone-task h3,
.content .done-task h3 {
font-family: font-Bold;
color: #828282;
}
.content .task-element {
border: 1px solid #e5e6e7;
border-radius: 3px;
box-shadow: 0px 10px 18px #e5e6e7;
margin-bottom: 20px;
min-height: 60px;
}
.task-element p {
font-family: font-Light;
color: #c5c5c5;
margin-bottom: 0;
font-size: 12px;
}
.task-element h4 {
font-family: font-Light;
color: #828282;
margin-top: 0px;
margin-bottom: 15px;
}
.task-element .task-left-element {
float: left;
margin-left: 15px;
}
.task-element .task-rigth-element {
margin-right: 10px;
float: right;
}
.checkmark {
float: right;
margin-right: 10px;
margin-top: 3%;
display: inline-block;
width: 22px;
height: 22px;
-ms-transform: rotate(45deg);
/* IE 9 */
-webkit-transform: rotate(45deg);
/* Chrome, Safari, Opera */
transform: rotate(45deg);
}
.checkmark_stem {
position: absolute;
width: 3px;
height: 9px;
background-color: #ccc;
left: 11px;
top: 6px;
}
.checkmark_kick {
position: absolute;
width: 3px;
height: 3px;
background-color: #ccc;
left: 8px;
top: 12px;
}
.checkmark:hover .checkmark_stem,
.checkmark:hover .checkmark_kick {
background-color: #6fcf97;
}
<div class="container">
<div class="content">
<h1>To-do</h1>
<form name="taskForm" onsubmit="taskList();return false">
<input type="text" name="new-task" id="new-task" placeholder="Task title">
<input type="submit" style="visibility: hidden;" />
</form>
<div class="undone-task">
<h3>Tasks</h3><span id="task-count"></span>
<div id="task-list"></div>
</div>
<div class="done-task">
<h3>Done</h3><span id="done-task-count"></span>
<div id="done-task-list"></div>
</div>
</div>
</div>

Well first of all...
You shouldn't generate HTML with JavaScript but instead use DOM queries to add/edit DOM children, for this purpose, you should learn how to use a library, like jQuery or vue.js.
Second of all, you shouldn't use onclick html attributes, but eventlisteners instead, this is usually handled by the library or framework..
Third, you're using index as a string, not as the variable, so of course it will try to use the global variable index which will be executed, way after the index loop is. So you should learn about lexical scope and variable management.
In order to solve your problem, replace onclick="changeStatus(index);" with onclick="changeStatus('+index+');" and it should work..

Related

How to create two Instances of the Simple Ajax Uploader Dropzone in One Project but in different modals

Two things not working:
During Upload Progress Bar is not Working
Implementation of this code in two different Bootstrap Modals but working separately and Independently in both modals.
I tried to change the ID progressContainer in HTML part of a Modal Body to progressContainer2 and var uploader in JavaScript to uploader2 thought may be it will work but by doing so uploaded file or ProgressContainer shows in just one Modal and other stops working.
How to make it work separately independent in both modals?
var uploader;
function createUploader(i, elem) {
var elem = $(elem);
var type = elem.data("type");
var dropzone = elem.find('.uploadDropzone');
var btn = elem.find('.btnUpload');
var progBox = elem.find('.uploadProgress');
var msgBox = elem.find('.uploadMessage');
var maxsize = 20000;
function showUploadMessage(msg, result) {
msgBox.text(msg).addClass(result).show();
}
function hideUploadMessage() {
msgBox.text('').removeClass('error').removeClass('unknown').removeClass('success').hide();
}
//create uploader(s)
uploader = new ss.SimpleUpload({
button: btn,
dropzone: dropzone,
/*url: 'your_php_file.php',*/
name: 'uploadfile',
progressBar: progBox,
multipart: true,
disabledClass: 'disabled',
responseType: 'json',
allowedExtensions: ["pdf"],
maxSize: maxsize,
multiple: true,
multipleSelect: true,
maxUploads: 10,
queue: false,
startXHR: function() {},
onSubmit: function(filename) {
hideUploadMessage(); //hide any previous errors
const progressBox = document.getElementById('progressContainer');
const wrapper = document.createElement('div')
wrapper.className = 'row ps-0 wrapper';
const fileNamef = document.createElement('div');
fileNamef.className = 'col-6 pe-0 name';
fileNamef.innerHTML = '<span>' + filename + '</span>';
const fileSize = document.createElement('div');
fileSize.className = 'col-2 d-flex justify-content-center size';
const progress = document.createElement('div');
progress.className = 'col-3 progress me-0 mt-2';
const bar = document.createElement('div');
bar.className = 'progress-bar';
const close = document.createElement('button');
close.className = ' col-1 d-flex justify-content-end deleteAttachment';
// Assemble the progress bar and add it to the page
wrapper.appendChild(fileNamef);
wrapper.appendChild(fileSize);
progress.appendChild(bar);
wrapper.appendChild(progress);
wrapper.appendChild(close);
progressBox.appendChild(wrapper);
$(wrapper).data('file', filename);
this.setProgressBar(bar);
this.setFileSizeBox(fileSize);
this.setAbortBtn(close);
//this.setProgressContainer(wrapper); // designate the containing div to be removed after upload
},
onSizeError: function() {
var msg = 'Files may not exceed ' + maxsize + 'k.';
showUploadMessage(msg, 'error');
},
onExtError: function() {
var msg = 'Please select only PDF file';
},
onComplete: function(filename, response) {
$('.wrapper').filterByData('file', filename).addClass('success'); //add a 'completed' class to the wrapper when file succeeds.
if (!response) {
var msg = 'Unable to upload file(s).';
showUploadMessage(msg, 'error');
return;
}
if (response.success === true) {
//things to do after complete, such as show image;
$(file.previewElement).find(".dz-image img").replaceWith('<i class="fa fa-file-pdf-o"></i>');
} else {
if (response.msg) {
var msg = escapeTags(response.msg);
showUploadMessage(msg, 'error');
} else {
var msg = 'Sorry! Unable to upload one of your files.';
showUploadMessage(msg, 'error');
}
}
},
onError: function() {
var msg = 'Unable to upload file(s) to server. Please try again later.';
showUploadMessage(msg, 'error');
$('.wrapper').removeClass('success').addClass('error');
}
});
}
$('.uploadContainer').each(createUploader);
$(document).on('click', '.deleteAttachment', function(e) {
e.preventDefault();
var wrapper = $(this).closest('.wrapper');
wrapper.remove();
// ... Back-end-dev should add a function to actually delete the image from the server.
});
.uploadContainer {
position: relative;
}
.uploadDropzone {
content: "";
border: 2px dashed #DADBDF;
padding: 20px;
text-align: center;
border-radius: 5px;
}
.uploadDropzone .cloud {
font-size: 48px;
line-height: 1;
opacity: .5;
}
.uploadMessage {
margin-top: .5rem;
}
.uploadProgress {
margin-top: .5rem;
background-color: #fff;
border: thin solid #aaa;
border-radius: 3px;
}
.uploadProgress:empty {
background: none !important;
margin: 0 !important;
border: none !important;
}
.wrapper {
width: auto;
}
.uploadProgress .wrapper {
position: static;
background-color: #fff;
white-space: nowrap;
border-bottom: thin solid #d2d2d2;
line-height: 35px;
padding: 0 0 0 10px;
}
.uploadProgress .wrapper:last-child {
border-bottom: 0;
}
.uploadProgress .wrapper:after {
content: "";
display: table;
clear: both;
}
.uploadProgress .name {
display: block;
float: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.uploadProgress .size {
display: block;
float: left;
white-space: nowrap;
color: #aaa;
padding-left: 5px;
}
.uploadProgress .size:before {
content: "(";
}
.uploadProgress .size:after {
content: ")";
}
.uploadProgress .progress {
display: block;
float: right;
margin-right: 10px
}
.uploadProgress .deleteAttachment {
display: block;
float: right;
padding: 0;
background-color: transparent !important;
border: 0 !important;
background-image: none;
line-height: inherit;
}
.deleteAttachment:after {
content: '\00D7';
display: block;
line-height: inherit;
width: 35px;
text-align: center;
color: rgba(0, 0, 0, .3);
font-family: sans-serif;
font-size: 18px;
font-weight: bold;
}
.uploadProgress .wrapper.success {
color: green;
}
.uploadProgress .wrapper.error {
color: red;
}
.uploadProgress .error .progress {
color: gold;
}
.uploadProgress .success .progress {
color: blue;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/6.0.0-beta.2/dropzone.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.0.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/6.0.0-beta.2/dropzone.js"></script>
<script src="https://btn.ninja/js/SimpleAjaxUploader.min.js"></script>
<div class="col-12 mt-3">
<div class="uploadContainer">
<button type="button" class="col-12 btn bg-transparent border-0 link btnUpload">
<div class="dotdash uploadDropzone">
<h5>Drag & Drop</h5>
<p>Your File Here or Browse to Upload</p>
<small class="text-prime">Only PDF Files with max size of 20 MB</small>
</div>
</button>
<div class="alert uploadMessage" style="display:none;"></div>
<div class="row">
<div id="progressContainer" class="col-12 uploadProgress"></div>
</div>
</div>
</div>

Question on element alignment and how cursor is able to stay flush with the text in this editable code textarea?

Looking at this codepen, most of it I grok. But a couple of things I don't understand:
How does the <code> element stay perfectly on top of the <textarea>? I would expect it to be below the textarea looking at the HTML code.
How is the cursor staying so well-aligned with the text such that it functions like the type of cursor in a word document? The cursor even aligns well with the text when I copy and paste the text. Is it the emmet dependency that's helping?
Here is the code:
HTML
<div class="editor-holder">
<ul class="toolbar">
<li><i class="fa fa-indent"></i></li>
<li><i class="fa fa-expand"></i></li>
</ul>
<div class="scroller">
<textarea class="editor allow-tabs"><div class="Editable Textarea">
<h1>This is a fully editable textarea which auto highlights syntax.</h1>
<p>Type or paste any code in here...</p>
<div>
<?php
var simple = "coding";
?>
<script>
with = "Tab or double space functionality";
</script></textarea>
<pre><code class="syntax-highight html"></code></pre>
</div>
</div>
(S)CSS
html, body{
margin: 0;
padding: 0;
height: 100%;
width: 100%;
position: relative;
background: rgb(114, 195, 195);
}
.editor-holder{
width: 800px;
height: 500px;
margin-top: 50px;
border-radius: 3px;
position: relative;
top: 0;
margin-left: -400px;
left: 50%;
background: #1f1f1f !important;
overflow: auto;
box-shadow: 5px 5px 10px 0px rgba(0, 0, 0, 0.4);
transition: all 0.5s ease-in-out;
&.fullscreen{
width: 100%;
height: 100%;
margin: 0;
left: 0;
}
.toolbar{
width: 100%;
list-style: none;
position: absolute;
top: -2px;
margin: 0;
left: 0;
z-index: 3;
padding: 8px;
background: #afafaf;
li{
display: inline-block;
}
a{
line-height: 20px;
background: rgba(144, 144, 144, 0.6);
color: grey;
box-shadow: inset -1px -1px 1px 0px rgba(0,0,0,0.28);
display: block;
border-radius: 3px;
cursor: pointer;
&:hover{
background: rgba(144, 144, 144, 0.8);
}
&.active{
background: rgba(144, 144, 144, 0.8);
box-shadow: none;
}
}
i{
color: #565656;
padding: 8px;
}
}
textarea, code{
width: 100%;
height: auto;
min-height: 450px;
font-size: 14px;
border: 0;
margin: 0;
top: 46px;
left: 0;
padding: 20px !important;
line-height: 21px;
position: absolute;
font-family: Consolas,Liberation Mono,Courier,monospace;
overflow: visible;
transition: all 0.5s ease-in-out;
}
textarea{
background: transparent !important;
z-index: 2;
height: auto;
resize: none;
color: #fff;
text-shadow: 0px 0px 0px rgba(0, 0, 0, 0);
text-fill-color: transparent;
-webkit-text-fill-color: transparent;
&::-webkit-input-placeholder{
color: rgba(255, 255, 255, 1);
}
&:focus{
outline: 0;
border: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
}
code{
z-index: 1;
}
}
pre {
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
code{
background: #1f1f1f !important;
color: #adadad;
.hljs {
color: #a9b7c6;
background: #282b2e;
display: block;
overflow-x: auto;
padding: 0.5em
}
.hljs-number,
.hljs-literal,
.hljs-symbol,
.hljs-bullet {
color: #6897BB
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-deletion {
color: #cc7832
}
.hljs-variable,
.hljs-template-variable,
.hljs-link {
color: #629755
}
.hljs-comment,
.hljs-quote {
color: #808080
}
.hljs-meta {
color: #bbb529
}
.hljs-string,
.hljs-attribute,
.hljs-addition {
color: #6A8759
}
.hljs-section,
.hljs-title,
.hljs-type {
color: #ffc66d
}
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #e8bf6a
}
.hljs-emphasis {
font-style: italic
}
.hljs-strong {
font-weight: bold
}
}
}
JavaScript
var tabCharacter = " ";
var tabOffset = 2;
$(document).on('click', '#indent', function(e){
e.preventDefault();
var self = $(this);
self.toggleClass('active');
if(self.hasClass('active'))
{
tabCharacter = "\t";
tabOffset = 1;
}
else
{
tabCharacter = " ";
tabOffset = 2;
}
})
$(document).on('click', '#fullscreen', function(e){
e.preventDefault();
var self = $(this);
self.toggleClass('active');
self.parents('.editor-holder').toggleClass('fullscreen');
});
/*------------------------------------------
Render existing code
------------------------------------------*/
$(document).on('ready', function(){
hightlightSyntax();
emmet.require('textarea').setup({
pretty_break: true,
use_tab: true
});
});
/*------------------------------------------
Capture text updates
------------------------------------------*/
$(document).on('ready load keyup keydown change', '.editor', function(){
correctTextareaHight(this);
hightlightSyntax();
});
/*------------------------------------------
Resize textarea based on content
------------------------------------------*/
function correctTextareaHight(element)
{
var self = $(element),
outerHeight = self.outerHeight(),
innerHeight = self.prop('scrollHeight'),
borderTop = parseFloat(self.css("borderTopWidth")),
borderBottom = parseFloat(self.css("borderBottomWidth")),
combinedScrollHeight = innerHeight + borderTop + borderBottom;
if(outerHeight < combinedScrollHeight )
{
self.height(combinedScrollHeight);
}
}
// function correctTextareaHight(element){
// while($(element).outerHeight() < element.scrollHeight + parseFloat($(element).css("borderTopWidth")) + parseFloat($(element).css("borderBottomWidth"))) {
// $(element).height($(element).height()+1);
// };
// }
/*------------------------------------------
Run syntax hightlighter
------------------------------------------*/
function hightlightSyntax(){
var me = $('.editor');
var content = me.val();
var codeHolder = $('code');
var escaped = escapeHtml(content);
codeHolder.html(escaped);
$('.syntax-highight').each(function(i, block) {
hljs.highlightBlock(block);
});
}
/*------------------------------------------
String html characters
------------------------------------------*/
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
/*------------------------------------------
Enable tabs in textarea
------------------------------------------*/
$(document).delegate('.allow-tabs', 'keydown', function(e) {
var keyCode = e.keyCode || e.which;
if (keyCode == 9) {
e.preventDefault();
var start = $(this).get(0).selectionStart;
var end = $(this).get(0).selectionEnd;
// set textarea value to: text before caret + tab + text after caret
$(this).val($(this).val().substring(0, start)
+ tabCharacter
+ $(this).val().substring(end));
// put caret at right position again
$(this).get(0).selectionStart =
$(this).get(0).selectionEnd = start + tabOffset;
}
});
How does code and textarea elements stay aligned?
They are aligned because in the CSS they both use the same style which outlines the positioning of both elements. They both use position: absolute and the same top and left properties so stay in the same position.
See here https://www.w3schools.com/css/css_positioning.asp
In terms of the z-axis you can see that code has a z-index of 1 while textarea has 2 so text area stays on top.
How is the cursor staying aligned with the text?
There is no javascript acting on the cursor here. If you were to have any textarea in html the cursor would align with the text.

Javascript - I have two event listeners which run the same global scope function, but one of them isn't working...why?

For a bit of context, I'm currently new to Javascript and programming in general. I'm currently making a to-do list using vanilla javascript.
I want the user to be able to add an entry by either clicking on the "+" button, or by pressing the enter key in the input field.
Definitions:
let count = 0;
let getAdd = document.getElementById('add')
let getBackground = document.getElementById('background')
let getInputs = document.getElementsByClassName('input')
let getItems = document.getElementsByClassName('item')
let getName = document.getElementById('name')
The "keypress" event listener is working, but the "click" event listener is not. Here's the part in question:
function addevent() {
if (document.getElementById('name').value === '') {
alert("You need to type something in the input field first!")
return
}
if (getItems.length == 0) {
count += 1;
getBackground.innerHTML = ''
getBackground.style = null;
getBackground.innerHTML += '<div class="item"><div class="column input"></div><div id="spacer" class="column"></div><div id="bin" class="bin column row">X</div></div>'
getInputs[count - 1].innerHTML = document.getElementById('name').value
let heightplus = getBackground.offsetHeight;
getBackground.style.height = parseInt(heightplus + 35) + "px"
document.getElementById('name').value = ''
}
else {
count += 1
getBackground.innerHTML += '<div class="item"><div class="column input"></div><div id="spacer" class="column"></div><div id="bin" class="bin column row">X</div></div>'
getInputs[count - 1].innerHTML = document.getElementById('name').value
let heightplus = getBackground.offsetHeight;
getBackground.style.height = parseInt(heightplus + 35) + "px"
document.getElementById('name').value = ''
}
}
getAdd.addEventListener("click", addevent(), false);
getName.addEventListener("keypress", function enter(e) {
if (e.keyCode === 13) {
addevent();
}
}, false);
What am I missing here?
If you need any further info, let me know.
let count = 0;
let getAdd = document.getElementById('add')
let getBackground = document.getElementById('background')
let getInputs = document.getElementsByClassName('input')
let getItems = document.getElementsByClassName('item')
let getName = document.getElementById('name')
function noitems() {
if (count == 0) {
getBackground.innerHTML = '<div class="start">Click on the <strong>+</strong> button to get started</div>'
}
else if (count == -1) {
getBackground.innerHTML = '<div class="end">No more tasks? Happy days!</div>'
count += 1
}
getBackground.style.paddingTop = "0px"
getBackground.style.boxShadow = "0px 0px 0px 0px"
getBackground.style.backgroundColor = "white"
}
window.onload = noitems();
function addevent() {
if (document.getElementById('name').value === '') {
alert("You need to type something in the input field first!")
return
}
if (getItems.length == 0) {
count += 1;
getBackground.innerHTML = ''
getBackground.style = null;
getBackground.innerHTML += '<div class="item"><div class="column input"></div><div id="spacer" class="column"></div><div id="bin" class="bin column row">X</div></div>'
getInputs[count - 1].innerHTML = document.getElementById('name').value
let heightplus = getBackground.offsetHeight;
getBackground.style.height = parseInt(heightplus + 35) + "px"
document.getElementById('name').value = ''
}
else {
count += 1
getBackground.innerHTML += '<div class="item"><div class="column input"></div><div id="spacer" class="column"></div><div id="bin" class="bin column row">X</div></div>'
getInputs[count - 1].innerHTML = document.getElementById('name').value
let heightplus = getBackground.offsetHeight;
getBackground.style.height = parseInt(heightplus + 35) + "px"
document.getElementById('name').value = ''
}
}
getAdd.addEventListener("click", addevent(), false);
getName.addEventListener("keypress", function enter(e) {
if (e.keyCode === 13) {
addevent();
}
}, false);
function doSomething(e) {
if (e.target.id === "bin") {
if (getItems.length == 1) {
let clickeditem = e.target
getBackground.removeChild(clickeditem.parentNode)
count -= 2
noitems();
}
else {
let clickeditem = e.target
getBackground.removeChild(clickeditem.parentNode)
let heightminus = getBackground.offsetHeight;
getBackground.style.height = parseInt(heightminus - 75) + "px"
count -= 1
}
}
e.stopPropagation();
}
getBackground.addEventListener("click", doSomething, false)
#import url('https://fonts.googleapis.com/css2?family=Roboto:wght#100&display=swap');
body {
font-family: 'Roboto', sans-serif;
-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, Opera and Firefox */
}
#title {
font-size: 32px;
margin-top: 1em;
border: 5px;
border-style: solid;
border-color: #001F5F;
width: 9em;
margin-left: calc(50% - 4.6em);
margin-right: calc(50% - 4.5em);
text-align: center;
}
#inputfield {
overflow: hidden;
padding-top: 5px;
padding-bottom: 5px;
margin-top: 50px;
margin-bottom: 10px;
}
::placeholder {
color: #E7E6E6;
opacity: 0.8;
}
#name {
height: 35px;
width: 813px;
outline: none;
background-color: #001F5F;
color: #E7E6E6;
text-align: left;
vertical-align: middle;
font-size: 22px;
box-shadow: 1px 2px 4px 2px darkgray;
margin-right: 10px;
border: 5px;
border-color: #E7E6E6;
float: left;
border-radius: 5px 5px 5px 5px;
}
#add {
height: 35px;
width: 35px;
background-color: #E7E6E6;
color: #001F5F;
font-size: 32px;
font-style: bold;
text-align: center;
vertical-align: middle;
line-height: 35px;
cursor: pointer;
box-shadow: 1px 2px 4px 2px darkgray;
float: left;
border-radius: 5px 5px 5px 5px;
}
#add:hover {
background-color:#001F5F;
color: #E7E6E6;
}
#background {
box-shadow: 0px 2px 4px 2px darkgray;
width: 900px;
height: 0px;
background-color: #E7E6E6;
padding-top: 20px;
border-radius: 5px 5px 5px 5px;
}
.start, .end {
text-align: center;
margin-top: 250px;
font-size: 32px;
padding: 0px;
vertical-align: middle;
}
#spacer {
width: 10px;
height: 35px;
background-color:#E7E6E6;
}
.input {
height: 35px;
width: 808px;
background-color:#001F5F;
padding-left: 5px;
border: 0px;
font-size: 22px;
color: #E7E6E6;
text-align: left;
vertical-align: middle;
outline: none;
box-shadow: 0px 2px 4px 2px darkgray;
border-radius: 5px 5px 5px 5px;
}
.bin {
width: 35px;
height: 35px;
font-size: 24px;
font-style: normal;
background-color: #E7E6E6;
color:#001F5F;
text-align: center;
vertical-align: middle;
line-height: 35px;
cursor: pointer;
border-radius: 5px 5px 5px 5px;
}
.bin:hover {
background-color:#001F5F;
color: #E7E6E6;
box-shadow: 0px 2px 4px 2px darkgray;
}
.item {
margin-left: 32px;
display: table;
table-layout: fixed;
width: 858px;
margin-bottom: 20px;
}
.column {
display: table-cell;
}
.thelist {
margin-left: calc(50% - 450px);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Oliver's To-Do List</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<h1 id="title">Oliver's To-Do List</h1>
<body>
<div class="thelist">
<div id="inputfield">
<input type="text" placeholder="Start typing here..."id="name">
<div id="add">+</div>
</div>
<div id="background">
</div>
</div>
<script src="main.js"></script>
</body>
</html>
Thanks!
getAdd.addEventListener("click", addevent(), false);
should be
getAdd.addEventListener("click", addevent, false);
As per this example from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener :
// Function to change the content of t2
function modifyText() {
const t2 = document.getElementById("t2");
if (t2.firstChild.nodeValue == "three") {
t2.firstChild.nodeValue = "two";
} else {
t2.firstChild.nodeValue = "three";
}
}
// Add event listener to table
const el = document.getElementById("outside");
el.addEventListener("click", modifyText, false);
Ok so I found out that within the getAdd event listener, the problem was the pair of brackets after the function name; once these are removed, it works just fine!
If anyone reading wants to add to this with their wisdom, knowledge and experience, or perhaps suggest any other improvements, please do!
Thanks!
Oh, you solved it. I just tried it out and modified something in the index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Oliver's To-Do List</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<h1 id="title">Oliver's To-Do List</h1>
<body>
<div class="thelist">
<div id="inputfield">
<input type="text" placeholder="Start typing here..."id="name">
<div id="add" onclick="addevent()">+</div>
</div>
<div id="background">
</div>
</div>
<script src="main.js"></script>
</body>
</html>
I added onclick="addevent()" , and It works

Is there a way to get my Stack Exchange stats?

I’m working on a re-creation of the flare image that Stack Exchange offers, and the re-creation is more responsive in that I can hover over a site icon and show my stats for a given Stack Exchange domain. I currently have to manually update my data which I plan to do twice a month or so, unless there’s a way to load that data directly from Stack Exchange via a web service or similar.
A few things to keep in mind:
I will be hosting this in an ASP.NET web application so C# APIs would be fine.
Web services would be perfect too since I can call them from JavaScript.
I would need links to documentation for any service provided.
Below is my current manual re-creation in case you’re curious or don’t know what the SE flair is, though it does need to be cleaned up and made to be more efficient.
var siteNames = [ 'Stack Exchange',
'Puzzling',
'Stack Overflow',
'Software Engineering',
'Mathematics',
'Physical Fitness' ]
var reps = [ '6.2k', '4.3k', '954', '410', '224', '220' ];
var golds = [ '1', '0', '0', '1', '0', '0' ];
var silvers = [ '14', '7', '4', '2', '1', '0' ];
var bronzes = [ '98', '50', '20', '10', '8', '10' ];
function getSiteStats(siteID) {
document.getElementById("site-name").innerText = siteNames[siteID];
document.getElementById("rep").innerText = reps[siteID];
document.getElementById("gold").innerText = golds[siteID];
document.getElementById("silver").innerText = silvers[siteID];
document.getElementById("bronze").innerText = bronzes[siteID];
}
function resetSiteStats() {
getSiteStats(0);
}
html, body {
margin: 0;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #6aa4ed;
background-image: linear-gradient(45deg, #6aa4ed, #141d33);
background-image: -webkit-linear-gradient(45deg, #6aa4ed, #141d33);
}
h1, h5 {
color: #fff;
font-family: Arial, Helvetica, sans-serif;
font-weight: 100;
text-align: center;
margin: 0;
}
h1 {
font-size: 10vh;
}
h5 {
margin-bottom: 10px;
}
.flair {
padding: 15px;
background-color: #fff;
border-radius: 5px;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25);
display: flex;
}
.flair img {
width: 40px;
height: 40px;
margin: 5px;
cursor: pointer;
}
.flair .profile {
width: 175px;
height: 175px;
margin: 0;
margin-right: 15px;
box-shadow: 2px 2px 4px rgba(12,13,14,0.5);
cursor: default;
}
.flair a {
color: #37f;
text-decoration: none;
margin: 5px;
}
.flair a:hover {
color: #15a;
}
.flair ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.flair ul > li {
display: inline-block;
margin: 5px;
}
.flair p {
margin: 0;
margin-left: 5px;
}
.badge div {
display: inline-block;
height: 7px;
width: 7px;
border-radius: 50%;
transform: translateY(-3px) translateX(3px);
}
.gold {
background-color: #fc0;
}
.silver {
background-color: #ccc;
}
.bronze {
background-color: #da6;
}
<h1>Stack Exchange Flair</h1>
<h5>Not Mobile Friendly (Yet)</h5>
<h5>Hover Over Site Icons</h5>
<div class="flair">
<img class="profile" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2940219/blue.jpg" />
<div class="account">
PerpetualJ
<p id="site-name">Stack Exchange</p>
<ul>
<li><strong id="rep">6.2k</strong></li>
<li>
<div class="badge">
<div class="gold"></div>
<span id="gold">1</span>
</div>
</li>
<li>
<div class="badge">
<div class="silver"></div>
<span id="silver">14</span>
</div>
</li>
<li>
<div class="badge">
<div class="bronze"></div>
<span id="bronze">98</span>
</div>
</li>
</ul>
<ul>
<li onmouseover="getSiteStats(1);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/puzzling/img/icon-48.png"/></li>
<li onmouseover="getSiteStats(2);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon.png"/></li>
<li onmouseover="getSiteStats(3);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/softwareengineering/img/icon-48.png"/></li>
<li onmouseover="getSiteStats(4);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/math/img/apple-touch-icon.png"/></li>
<li onmouseover="getSiteStats(5);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/fitness/img/icon-48.png?v=f5a02f85db94"/></li>
</ul>
<p>How fast do you have to slap a chicken to cook it?</p>
</div>
</div>
Is there some way for me to call an API, web service, or similar that will allow me to pull my current stats for a given Stack Exchange site?
Also, I would prefer to not do any type of web scraping or similar. I’d prefer it come from a legitimate Stack Exchange service.
NOTE: If this belongs on meta please let me know so it can be migrated.
On-Topic: This question is considered as on-topic per the help center:
We feel the best Stack Overflow questions have a bit of source code in them, but if your question generally covers…
software tools commonly used by programmers; and is
a practical, answerable problem that is unique to software development
…then you’re in the right place to ask your question!
Given the above quote, API's are tools commonly used by programmers, and by asking if Stack Exchange has one, this question is a practical and answerable problem. However, I do believe this may have been better suited for Meta, but I am unable to migrate it.
I found out recently that Stack Exchange does offer an API for these types of things. I heavily recommend reading over their documentation for the API prior to usage. In order to accomplish the task I've asked about here, I needed to utilize the following API calls:
/users/{ids}
/users/{ids}/associated
I utilized both of these calls together to recreate the Stack Exchange flair, and just-in-case you do not know what the flair is:
To get started I wrote a simple set of methods to process my requests to the API:
function getWebServiceResponse(requestUrl, callback) {
let request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function() {
if (request.status < 200 || request.status >= 400)
callback("An unexpected error occurred.");
else
callback(JSON.parse(this.response));
};
request.send();
}
function getSEWebServiceResponse(request, callback) {
let apiRoot = 'https://api.stackexchange.com/2.2/';
let key = 'key=s29XM)Eqn2x3YxhjLgFwBQ((';
if (request.indexOf('?') >= 0)
key = '&' + key;
else
key = '?' + key;
getWebServiceResponse(apiRoot + request + key, function(response) { callback(response); });
}
Here, the key is needed to help prevent throttling of too many subsequent requests:
Every application is subject to an IP based concurrent request throttle. If a single IP is making more than 30 requests a second, new requests will be dropped.
From here the implementation is pretty straight-forward and was a great learning process!
/users/{ids}
Gets the users identified in ids in {ids}.
Typically this method will be called to fetch user profiles when you have obtained user ids from some other source, such as /questions.
{ids} can contain up to 100 semicolon delimited ids.
function getAssociatedAccountDetails(userID, siteName, fullSiteName, callback) {
let url = 'users/' + userID +'?order=desc&sort=reputation&site=' + siteName;
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
let account = response.items[0];
userCard.reputation += account.reputation;
userCard.badges.gold += account.badge_counts.gold;
userCard.badges.silver += account.badge_counts.silver;
userCard.badges.bronze += account.badge_counts.bronze;
if (userCard.siteUrls.length < 7) {
var siteProfileCombo = account.link + '|<IMG>|' + fullSiteName;
siteProfileCombo = siteProfileCombo.replace('<IMG>', getSiteIcon(siteName));
userCard.siteUrls.push(siteProfileCombo);
}
if (userCard.username.length < 1)
userCard.username = account.display_name;
if (userCard.profileImageUrl.length < 1)
userCard.profileImageUrl = account.profile_image;
callback();
});
}
/users/{ids}/associated
Returns all of a user's associated accounts, given their account_ids in {ids}.
{ids} can contain up to 100 semicolon delimited ids.
function getAssociatedAccounts(accountID, callback) {
let url = 'users/' + accountID + '/associated';
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
var accounts = sortAccountsByReputation(response.items);
var accountsProcessed = 0;
for (let i = 0; i < accounts.length; i++) {
let siteName = accounts[i].site_url.replace('https://', '');
siteName = siteName.replace('.stackexchange', '');
siteName = siteName.replace('.com', '');
getAssociatedAccountDetails(accounts[i].user_id, siteName, accounts[i].site_name, function() {
if (++accountsProcessed >= accounts.length)
callback();
});
}
});
}
The Full Implementation
/* Definitions */
var CardType = { Wheel: "wheel", Card: "card", Box: "box" }
var userCard = {
username: '',
profileImageUrl: '',
reputation: 0,
badges: {
gold: 0,
silver: 0,
bronze: 0
},
siteUrls: []
}
/* Initial Calls */
var accountID = '13342919';
generateCard('user-flair-wheel', accountID, CardType.Wheel);
/* Required Events */
function showSitename(tooltipID, siteName) {
var tooltip = document.getElementById(tooltipID);
tooltip.innerHTML = siteName.replace('Stack Exchange', '');
tooltip.classList.add('active');
}
function hideSitename(tooltipID) {
document.getElementById(tooltipID).classList.remove('active');
}
/* UI Generation Functions */
function generateCard(containerid, accountid, cardType) {
getAssociatedAccounts(accountID, function() {
var className = cardType.toString().toLowerCase();
var container = document.getElementById(containerid);
container.classList.add("flair");
container.classList.add(className);
// Build the card.
addProfile(container);
addScores(container, className);
addSites(container, className);
container.innerHTML += '<div id="' + containerid +
'-tooltip" class="se-tooltip"></div>';
});
}
function addProfile(container) {
container.innerHTML += '<img class="user-image" src="' +
userCard.profileImageUrl + '"/>';
container.innerHTML += '<h1 class="username display-4">' +
userCard.username + '</h1>';
}
function addScores(container, cardType) {
var badges = '<ul class="badges">';
badges += '<li><i class="fas fa-trophy"></i> <span id="reputation-' +
cardType + '">' + userCard.reputation + '</span></li>';
badges += '<li><span id="gold-badges-' + cardType + '">' +
userCard.badges.gold + '</span></li>';
badges += '<li><span id="silver-badges-' + cardType + '">' +
userCard.badges.silver + '</span></li>';
badges += '<li><span id="bronze-badges-' + cardType + '">' +
userCard.badges.bronze + '</span></li>';
badges += '</ul>';
container.innerHTML += badges;
}
function addSites(container, cardType) {
var sites = '<ul id="sites-' + cardType + '" class="sites">';
for (var i = 0; i < userCard.siteUrls.length; i++) {
var site = '<li>';
var siteLinkSplit = userCard.siteUrls[i].split('|');
site += '<a href="' + siteLinkSplit[0] + '">';
var tooltipID = container.id +'-tooltip';
var linkElement = '<a href="' + siteLinkSplit[0] + '"';
linkElement += ' onmouseover="showSitename(\'' + tooltipID + '\',\'' + siteLinkSplit[2] + '\')"';
linkElement += ' onmouseout="hideSitename(\'' + tooltipID + '\');"';
site += linkElement + '>';
site += '<img src="' + (siteLinkSplit[1] == '<IMG>' ? '#' : siteLinkSplit[1]) + '"/></a></li>';
sites += site;
}
sites += '</ul>';
container.innerHTML += sites;
}
/* Stack Exchange API Based Functions */
function getAssociatedAccounts(accountID, callback) {
let url = 'users/' + accountID + '/associated';
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
var accounts = sortAccountsByReputation(response.items);
var accountsProcessed = 0;
for (let i = 0; i < accounts.length; i++) {
let siteName = accounts[i].site_url.replace('https://', '');
siteName = siteName.replace('.stackexchange', '');
siteName = siteName.replace('.com', '');
getAssociatedAccountDetails(accounts[i].user_id, siteName, accounts[i].site_name, function() {
if (++accountsProcessed >= accounts.length)
callback();
});
}
});
}
function getAssociatedAccountDetails(userID, siteName, fullSiteName, callback) {
let url = 'users/' + userID +'?order=desc&sort=reputation&site=' + siteName;
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
let account = response.items[0];
userCard.reputation += account.reputation;
userCard.badges.gold += account.badge_counts.gold;
userCard.badges.silver += account.badge_counts.silver;
userCard.badges.bronze += account.badge_counts.bronze;
if (userCard.siteUrls.length < 7) {
var siteProfileCombo = account.link + '|<IMG>|' + fullSiteName;
siteProfileCombo = siteProfileCombo.replace('<IMG>', getSiteIcon(siteName));
userCard.siteUrls.push(siteProfileCombo);
}
if (userCard.username.length < 1)
userCard.username = account.display_name;
if (userCard.profileImageUrl.length < 1)
userCard.profileImageUrl = account.profile_image;
callback();
});
}
/* Helper Functions */
function getSEWebServiceResponse(request, callback) {
let apiRoot = 'https://api.stackexchange.com/2.2/';
let key = 'key=s29XM)Eqn2x3YxhjLgFwBQ((';
if (request.indexOf('?') >= 0)
key = '&' + key;
else
key = '?' + key;
getWebServiceResponse(apiRoot + request + key, function(response) { callback(response); });
}
function getWebServiceResponse(requestUrl, callback) {
let request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function() {
if (request.status < 200 || request.status >= 400)
callback("An unexpected error occurred.");
else
callback(JSON.parse(this.response));
};
request.send();
}
function sortAccountsByReputation(accounts) {
return accounts.sort(function(a, b) { return b.reputation - a.reputation; });
}
function getSiteIcon(siteName) {
if (siteName == "meta")
return 'https://meta.stackexchange.com/content/Sites/stackexchangemeta/img/icon-48.png';
return 'https://cdn.sstatic.net/Sites/' + siteName + '/img/apple-touch-icon.png';
}
/* Flair Styles */
.flair {
position: relative;
margin: 15px;
}
.flair > .se-tooltip {
position: absolute;
left: 50%;
transform: translate(-50%);
width: 250px;
bottom: 50px;
opacity: 0;
background-color: #fff;
color: #555;
text-shadow: none;
border-radius: 25px;
padding: 5px 10px;
box-shadow: 2px 2px 3px #0005;
}
.flair > .se-tooltip.active {
bottom: 10px;
opacity: 1;
}
/* Flair Wheel Styles */
.flair.wheel {
width: 200px;
height: 250px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-shadow: 1px 1px 2px #0005;
}
.flair.wheel .user-image {
width: 100px;
height: 100px;
border-radius: 50%;
box-shadow: 2px 2px 3px #0005;
}
.flair.wheel .username {
font-size: 30px;
margin: 0;
}
.flair.wheel .badges > li > span { position: relative; }
.flair.wheel .badges > li:first-of-type > i { color: #5c9; }
.flair.wheel .badges > li:not(:first-of-type) > span::before {
content: '';
position: absolute;
top: 50%;
left: -15px;
transform: translateY(-40%);
width: 10px;
height: 10px;
border-radius: 50%;
}
.flair.wheel .badges > li:nth-child(2) > span::before { background-color: #fb3; }
.flair.wheel .badges > li:nth-child(3) > span::before { background-color: #aaa; }
.flair.wheel .badges > li:nth-child(4) > span::before { background-color: #c95; }
.flair.wheel .sites {
position: absolute;
top: 10px;
left: 0;
width: 100%;
height: 55%;
}
.flair.wheel .sites > li { position: absolute; }
.flair.wheel .sites > li > a > img {
width: 35px;
height: 35px;
background-color: #fffa;
border-radius: 50%;
padding: 2px;
box-shadow: 2px 2px 3px #0005;
cursor: pointer;
transition: 0.3s cubic-bezier(0.5, -2.5, 1.0, 1.2) all;
z-index: 1;
}
.flair.wheel .sites > li > a:hover > img {
width: 40px;
height: 40px;
background-color: #fff;
}
.flair.wheel .sites > li:nth-child(1) {
top: -15px;
left: 50%;
transform: translate(-50%);
}
.flair.wheel .sites > li:nth-child(2) {
top: 0px;
left: 15%;
transform: translate(-20%);
}
.flair.wheel .sites > li:nth-child(3) {
top: 0px;
left: 70%;
transform: translate(-20%);
}
.flair.wheel .sites > li:nth-child(4) {
top: 45%;
left: 80%;
transform: translate(-20%, -50%);
}
.flair.wheel .sites > li:nth-child(5) {
top: 45%;
left: -5px;
transform: translateY(-50%);
}
.flair.wheel .sites > li:nth-child(6) {
top: 79%;
left: 3px;
transform: translateY(-50%);
}
.flair.wheel .sites > li:nth-child(7) {
top: 79%;
right: 3px;
transform: translateY(-50%);
}
/* To Organize in a Row instead of Column */
.user-flair-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
/* Global Styles */
ul {
padding: 0;
listy-style-type: none;
}
ul > li {
display: inline-block;
padding: 0 10px;
}
/* Template Overrides */
html, body {
margin: 0;
height: 100%;
background-color: #333 !important;
background-image: linear-gradient(45deg, #333, #555) !important;
background-image: -webkit-linear-gradient(45deg, #333, #555) !important;
}
.primary-content {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.primary-content > .lead { font-size: 25px; }
<link href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2940219/PerpetualJ.css" rel="stylesheet"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" rel="stylesheet"/>
<div id="primary-content" class="primary-content">
<div class="user-flair-container">
<div id="user-flair-wheel"></div>
</div>
</div>
Best of luck to all of you in your future endeavors!

Call function destroyed callback in last call

I have function, which create popup similar as alert, confirm or prompt. Problem occurs when is called new popup (any type), then is destroyed callback in first call (prompt value)
PoppyPopup.prompt('Type your name', 'User control', {}, function(value) {
PoppyPopup.alert('Welcome ' + value); //<-- value is id of second popup
});
PoppyPopup.alert('Hi, this is a PopUp!', 'With title!');
DEMO: https://jsfiddle.net/yojz8sb3/
Edit: When is call one time, it's OK, I really dont know where is problem.
Well, the short answer is because PoppyPopup isn't designed to handle having more than one popup open at a time.
popupType is shared between all popups so when you open the alert popup it sets popupType to ALERT and so when the prompt popup's accept callback is called it's handled as if it's an ALERT type popup.
One (fairly hacky) solution is to remove var popupType = null; and instead pass popupType to the Popup constructor as a 4th argument. Then you can change
PoppyPopup.accept(basePopup.id, options);
to
PoppyPopup.accept(basePopup.id, options, popupType);
in Popup function.
And change
accept: function(popupId, options) {
to
accept: function(popupId, options, popupType) {
that way the popup type is associated with the popup instance instead of being shared among all popups.
Here is a working example: https://jsfiddle.net/0xpz7f9L/
However my answer above doesn't fix the issue that clicking the accept button appears to close a random popup (obviously not ideal). I'd recommend rewriting the whole thing to simplify it and make it easier to read/understand. Also this sort of thing traditionally uses promises instead of callbacks.
Here is how I would rewrite it:
const PoppyPopup = (function() {
let nextPopupId = 0;
return {
alert: function(message, title, options) {
const realOptions = buildOptionsFrom(options);
const popupElem = buildPopupElem(title, message, realOptions, false, false);
const promise = new Promise(resolve => {
popupElem.querySelector('.accept').onclick = resolve;
});
promise.finally(() => close(popupElem, realOptions.removeWhenClosed));
document.querySelector('body').appendChild(popupElem);
return promise;
},
confirm: function(message, title, options) {
const realOptions = buildOptionsFrom(options);
const popupElem = buildPopupElem(title, message, realOptions, false, true);
const promise = new Promise((resolve, reject) => {
popupElem.querySelector('.accept').onclick = resolve;
popupElem.querySelector('.cancel').onclick = reject;
});
promise.finally(() => close(popupElem, realOptions.removeWhenClosed));
document.querySelector('body').appendChild(popupElem);
return promise;
},
prompt: function(message, title, options) {
const realOptions = buildOptionsFrom(options);
const popupElem = buildPopupElem(title, message, realOptions, true, true);
const promise = new Promise((resolve, reject) => {
popupElem.querySelector('.accept').onclick = () => resolve(popupElem.querySelector('input').value);
popupElem.querySelector('.cancel').onclick = reject;
});
promise.finally(() => close(popupElem, realOptions.removeWhenClosed));
document.querySelector('body').appendChild(popupElem);
return promise;
}
};
function close(popupElem, removeWhenClosed) {
popupElem.classList.remove('show');
popupElem.addEventListener('transitionend', function(e) {
if (e.propertyName === 'opacity' && removeWhenClosed) {
popupElem.parentNode.removeChild(popupElem);
}
});
}
function buildOptionsFrom(options) {
return Object.assign({
showBackground: true,
removeWhenClosed: true,
width: '400px'
}, options || {});
}
function buildPopupElem(title, message, options, provideInput, provideCancel) {
const basePopupDiv = document.createElement("DIV");
basePopupDiv.className = "poppy-popup show";
basePopupDiv.id = nextPopupId++;
if (options.showBackground) {
const backgroundDiv = document.createElement("DIV");
backgroundDiv.className = "poppy-popup-background";
basePopupDiv.appendChild(backgroundDiv);
}
const containerDiv = document.createElement("DIV");
containerDiv.className = "poppy-popup-container";
containerDiv.style.width = options.width;
containerDiv.style.height = options.height;
if (title) {
const headerTitleDiv = document.createElement("DIV");
headerTitleDiv.className = "poppy-popup-title-text";
headerTitleDiv.innerHTML = title;
const headerDiv = document.createElement("DIV");
headerDiv.className = "poppy-popup-header";
headerDiv.appendChild(headerTitleDiv);
containerDiv.appendChild(headerDiv);
}
const contentDiv = document.createElement("DIV");
contentDiv.className = "poppy-popup-content";
contentDiv.innerHTML = message;
if (provideInput) {
const input = document.createElement("INPUT");
input.type = "text";
contentDiv.appendChild(input);
}
const acceptLink = document.createElement("A");
acceptLink.className = 'accept';
acceptLink.href = "#";
acceptLink.innerHTML = "<i class='material-icons'>done</i> OK";
const acceptSpan = document.createElement("SPAN");
acceptSpan.className = "poppy-popup-accept";
acceptSpan.appendChild(acceptLink);
const buttonsDiv = document.createElement("DIV");
buttonsDiv.className = "poppy-popup-buttons";
buttonsDiv.appendChild(acceptSpan);
if (provideCancel) {
const cancelLink = document.createElement("A");
cancelLink.className = "cancel";
cancelLink.href = "#";
cancelLink.innerHTML = "<i class='material-icons'>close</i> Cancel";
const cancelSpan = document.createElement("SPAN");
cancelSpan.className = "poppy-popup-cancel";
cancelSpan.appendChild(cancelLink);
buttonsDiv.appendChild(cancelSpan);
}
containerDiv.appendChild(contentDiv);
containerDiv.appendChild(buttonsDiv);
basePopupDiv.appendChild(containerDiv);
return basePopupDiv;
} //end of buildPopupElem()
})();
PoppyPopup.prompt('Type your name', 'User control').then(value => {
PoppyPopup.alert('Welcome ' + value).then(() => {
PoppyPopup.confirm('And just for completeness, confirm?')
.then(() => alert('accepted'), () => alert('rejected'));
});
});
PoppyPopup.alert('Hi, this is a PopUp!', 'With title!');
*, *:before, *:after {
box-sizing: border-box; }
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-family: 'Roboto', sans-serif;
font-size: 16px;
background-color: #eeeeee; }
.poppy-popup {
position: fixed;
top: 0;
left: 0;
z-index: 1000;
width: 100vw;
height: 100vh;
display: -ms-flexbox;
display: flex;
-ms-flex-pack: center;
justify-content: center;
-ms-flex-align: center;
align-items: center;
font-size: 16px;
opacity: 0;
transition: opacity .3s; }
.poppy-popup.show {
opacity: 1; }
.poppy-popup > .poppy-popup-background {
position: fixed;
top: 0;
left: 0;
z-index: 1010;
background-color: rgba(0, 0, 0, 0.5);
width: 100%;
height: 100%; }
.poppy-popup > .poppy-popup-container {
max-width: 90%;
max-height: 90%;
width: 100%;
position: relative;
z-index: 1020;
border-radius: 3px;
box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.3); }
.poppy-popup > .poppy-popup-container > .poppy-popup-header {
background-color: #FFFFFF;
color: #222222;
height: 50px;
width: 100%;
position: relative;
border-top-left-radius: 3px;
border-top-right-radius: 3px; }
.poppy-popup > .poppy-popup-container > .poppy-popup-header > .poppy-popup-title-text {
width: 100%;
max-height: 50px;
font-size: 1.5em;
text-align: center;
line-height: 50px;
text-overflow: ellipsis;
font-weight: bold;
overflow: hidden;
white-space: nowrap; }
.poppy-popup > .poppy-popup-container > .poppy-popup-content {
background-color: #FFFFFF;
width: 100%;
padding: 10px 20px;
border-left: 1px solid #DDDDDD;
border-right: 1px solid #DDDDDD;
overflow: auto; }
.poppy-popup > .poppy-popup-container > .poppy-popup-content > input[type="text"] {
background-color: transparent;
border-width: 0;
border-bottom: 2px solid #CCCCCC;
outline: none;
font-size: 1.3em;
width: 100%;
margin-top: 20px;
padding: 5px;
box-shadow: none;
transition: border-bottom .2s; }
.poppy-popup > .poppy-popup-container > .poppy-popup-content > input[type="text"]:focus {
border-bottom: 2px solid #2088AD; }
.poppy-popup > .poppy-popup-container > .poppy-popup-buttons {
width: 100%;
height: 50px;
padding: 0 10px;
background-color: #FFFFFF;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
overflow: hidden; }
.poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-accept, .poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-cancel {
width: 120px;
display: block;
float: right;
color: #2088AD; }
.poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-accept > a, .poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-cancel > a {
display: block;
color: inherit;
text-decoration: none;
text-align: center;
padding: 10px 0;
border-radius: 3px;
transition: background-color .2s, box-shadow .1s; }
.poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-accept > a:active, .poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-cancel > a:active {
box-shadow: inset 0 0 5px 1px rgba(0, 0, 0, 0.3); }
.poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-accept > a > i, .poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-cancel > a > i {
vertical-align: middle;
margin-top: -3px; }
.poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-accept {
right: 0;
color: #2088AD; }
.poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-accept > a:hover {
background-color: rgba(0, 0, 0, 0.1); }
.poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-cancel {
left: 0;
color: #2088AD; }
.poppy-popup > .poppy-popup-container > .poppy-popup-buttons > .poppy-popup-cancel > a:hover {
background-color: rgba(0, 0, 0, 0.1); }
Note: The css is unchanged.

Categories

Resources