Related
On mobile, it's a common UI pattern to have a scrollable element inside a draggable element. When you reach the end of the scrollable element, you start dragging the outer element. E.g. in this GIF (https://media.giphy.com/media/9MJgBkoZfqA7jRdQop/giphy.gif), after scrolling to the top, if you continuing scrolling, it'll drag the subreddits menu.
I want to implement a similar pattern using JS/CSS. To do this, I need to detect if users continue scrolling after reaching the end. Is this possible? If so, is it possible to determine how much they scroll after reaching the end?
window.onscroll = function(element) {
if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) {
alert("you're at the bottom of the page");
}
};
Using element parameter to know the current exact x y where mouse is now at to calculate more and some how much was scrolled
Javascript: How to detect if browser window is scrolled to bottom?
If You need to keep track of the user activity after the bottom (or the top) of the page has been reached, beside the scroll event, You need to track the the wheel event. Moreover, on mobile, You need to track also touchstart and touchmove events.
Not all these events are normalized across browsers, so I did my own normalization function, which is more or less something like this:
var compulsivity = Math.log2(Math.max(scrollAmount, 0.01) * wheelAmount);
Below is a complete playground. You can test it in Chrome using the Mobile View of the Developer Tools, or in other browsers using the TouchEmulator.
function Tracker(page) {
this.page = page;
this.moveUp = 0;
this.moveDown = 0;
this.startTouches = {};
this.moveTouches = {};
this.lastScrollY = 0;
this.monitor = {};
this.startThreshold = 160;
this.moveThreshold = 10;
this.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
this.pullToRefresh = window.chrome || navigator.userAgent.match('CriOS');
this.amplitude = 16 / Math.log(2);
this.page.ownerDocument.addEventListener( 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll', this, { passive: true } );
/* The basic scroll event cannot be canceled, so it does not need to be set passive.*/
this.page.ownerDocument.addEventListener('scroll', this);
this.page.addEventListener('touchstart', this, { passive: true });
/* Maybe we need to cancel pullToRefresh */
this.page.addEventListener('touchmove', this, { passive: false });
return this;
}
Tracker.prototype.handleEvent = function (e) { /* handleEvent is built-in */
var winHeight = (this.iOS ? document.documentElement.clientHeight : window.innerHeight) | 0,
currScrollY = window.pageYOffset | 0,
amountScrollY = (this.lastScrollY - currScrollY) | 0,
elHeight = this.page.offsetHeight | 0,
elTop = -currScrollY, elBottom = winHeight - elHeight + currScrollY,
isTop = elTop >= 0, isBottom = elBottom >= 0;
switch (e.type) {
case 'wheel':
case 'onmousewheel':
case 'mousewheel':
case 'DOMMouseScroll':
var wheelDelta = e.wheelDelta ? e.wheelDelta : e.deltaY ? -e.deltaY : -e.detail,
wheelDir = (wheelDelta > 0) - (wheelDelta < 0),
wheelUp = wheelDir < 0, wheelDown = wheelDir > 0,
wheelAmount = 100 * wheelDir;
if (isTop && wheelDown) {
this.moveUp++;
this.moveDown = 0;
} else if (isBottom && wheelUp) {
this.moveUp = 0;
this.moveDown++;
} else {
this.moveUp = 0;
this.moveDown = 0;
}
var compulsivity = this.amplitude * Math.log(Math.max(this.moveUp, this.moveDown, 0.01) * wheelAmount* wheelDir);
this.monitor[e.type].track(wheelAmount, compulsivity);
break;
case 'scroll':
/* end of scroll event for iOS, start/end of scroll event for other browsers */
this.lastScrollY = currScrollY;
this.monitor[e.type].track(amountScrollY, 0);
break;
case 'touchstart':
var touches = [].slice.call(e.touches), i = touches.length;
while (i--) {
var touch = touches[i], id = touch.identifier;
this.startTouches[id] = touch;
this.moveTouches[id] = touch;
}
break;
case 'touchmove':
var touches = [].slice.call(e.touches), i = touches.length,
currTouches = {},
swipeUp = false, swipeDown = false,
currMoveY = 0, totalMoveY = 0;
while (i--) {
var touch = touches[i], id = touch.identifier;
currTouches[id] = touch;
if (id in this.moveTouches) {
currMoveY = this.moveTouches[id].screenY - touch.screenY;
}
if (id in this.startTouches) {
totalMoveY = this.startTouches[id].screenY - touch.screenY;
}
swipeUp = currMoveY > 0 || totalMoveY > 0;
swipeDown = currMoveY < 0 || totalMoveY < 0;
if (this.pullToRefresh && isTop && swipeDown && e.cancelable) {
e.preventDefault();
console.log('Reload prevented');
}
}
this.moveTouches = currTouches;
var moveDir = (totalMoveY > 0) - (totalMoveY < 0),
longSwipe = moveDir * totalMoveY > this.startThreshold,
shortSwipe = moveDir * totalMoveY > this.moveThreshold,
realSwipe = longSwipe || shortSwipe;
if (isTop && swipeDown) {
if (realSwipe) this.moveUp++;
this.moveDown = 0;
} else if (isBottom && swipeUp) {
this.moveUp = 0;
if (realSwipe) this.moveDown++;
} else {
this.moveUp = 0;
this.moveDown = 0;
}
var compulsivity = this.amplitude * Math.log(Math.max(this.moveUp, this.moveDown, 0.01) * moveDir * totalMoveY);
this.monitor[e.type].track(currMoveY, compulsivity);
break;
}
};
function Monitor(events) {
this.ctx = null;
this.cont = null;
this.events = events;
this.values = [];
this.average = 0;
this.lastDrawTime = 0;
this.inertiaDuration = 200;
return this;
}
Monitor.prototype.showOn = function (container) {
var cv = document.createElement('canvas');
this.ctx = cv.getContext('2d');
this.cont = document.getElementById(container);
cv.width = this.cont.offsetWidth;
cv.height = this.cont.offsetHeight;
cv.style.top = 0;
cv.style.left = 0;
cv.style.zIndex = -1;
cv.style.position = 'absolute';
cv.style.backgroundColor = '#000';
this.cont.appendChild(cv);
var self = this;
window.addEventListener('resize', function () {
var cv = self.ctx.canvas, cont = self.cont;
cv.width = cont.offsetWidth;
cv.height = cont.offsetHeight;
});
return this;
};
Monitor.prototype.track = function (value, average) {
this.average = average;
if (this.values.push(value) > this.ctx.canvas.width) this.values.shift();
if (value) this.lastDrawTime = new Date().getTime();
};
Monitor.prototype.draw = function () {
if (this.ctx) {
var cv = this.ctx.canvas, w = cv.width, h = cv.height;
var i = this.values.length, x = w | 0, y = (0.5 * h) | 0;
cv.style.backgroundColor = 'rgb(' + this.average + ', 0, 0)';
this.ctx.clearRect(0, 0, w, h);
this.ctx.strokeStyle = '#00ffff';
this.ctx.lineWidth = 1;
this.ctx.beginPath();
while (i--) {
x -= 4;
if (x < 0) break;
this.ctx.moveTo(x, y);
this.ctx.lineTo(x + 1, y);
this.ctx.lineTo(x + 1, y - this.values[i]);
}
this.ctx.stroke();
var elapsed = new Date().getTime() - this.lastDrawTime;
/* cool down */
this.average = this.average > 0 ? (this.average * 0.9) | 0 : 0;
if (elapsed > this.inertiaDuration) {
this.track(0, this.average);
}
}
var self = this;
setTimeout(function () {
self.draw();
}, 100);
};
Monitor.prototype.connectTo = function (tracker) {
var events = this.events.split(' '), i = events.length;
while (i--) {
tracker.monitor[events[i]] = this;
}
this.draw();
return this;
};
function loadSomeData(target) {
$.ajax({
url: 'https://jsonplaceholder.typicode.com/users',
method: 'GET',
crossDomain: true,
dataType: 'json',
success: function (users) {
var html = '', $ul = $(target).find('ul');
$.each(users, function (i, user) {
var item = '<li><a class="ui-alt-icon ui-nodisc-icon">';
item += '<h2>' + user.name + '</h2>';
item += '<p><strong>' + user.company.name + '</strong></p>';
item += '<p>' + user.address.zipcode + ', ' + user.address.city + '</p>';
item += '<p>' + user.phone + '</p>';
item += '<p>' + user.email + '</p>';
item += '<p class="ui-body-inherit ui-li-aside ui-li-count"><strong>' + user.id + '</strong></p>';
item += '</a></li>';
html += item;
});
$ul.append(html).listview('refresh');
},
});
}
$(document)
.on('pagecreate', '#page-list', function (e) {
$("[data-role='header'], [data-role='footer']").toolbar({ theme: 'a', position: 'fixed', tapToggle: false });
loadSomeData(e.target);
})
.on('pageshow', '#page-list', function (e, ui) {
var tracker = $.data(this, 'mobile-page', new Tracker(this));
new Monitor('touchstart touchmove').connectTo(tracker).showOn('header');
new Monitor('scroll wheel mousewheel DOMMouseScroll').connectTo(tracker).showOn('footer');
});
.ui-page {
touch-action: none;
}
h1, h2, h3, h4, h5, h6, p {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* JQM no frills */
.ui-btn,
.ui-title,
.ui-btn:hover,
.ui-btn:focus,
.ui-btn:active,
.ui-btn:visited {
text-shadow: none !important;
}
* {
-webkit-box-shadow: none !important;
-moz-box-shadow: none !important;
box-shadow: none !important;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Compulsivity</title>
<meta name="description" content="Compulsivity" />
<meta name="HandheldFriendly" content="True" />
<meta name="MobileOptimized" content="320" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, width=device-width, minimal-ui shrink-to-fit=no" />
<meta http-equiv="cleartype" content="on" />
<!-- Add to homescreen for Chrome on Android -->
<meta name="mobile-web-app-capable" content="yes" />
<!-- For iOS web apps. Delete if not needed. -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="Compulsivity" />
<link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<!--
<script type="application/javascript" src="lib/touch-emulator.js"></script>
<script> TouchEmulator(); </script>
-->
<script type="application/javascript" src="https://cdn.jsdelivr.net/npm/jquery#2.2.4/dist/jquery.min.js"></script>
<script type="application/javascript" src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
</head>
<body>
<div id="header" data-role="header"><h4 style="color: #fff">Compulsivity</h4></div>
<div id="page-list" data-role="page">
<div data-role="content" role="main">
<ul data-role="listview" data-filter="true" data-inset="true"></ul>
</div>
</div>
<div id="footer" data-role="footer"><h4 style="color: #fff">Scroll</h4></div>
</body>
</html>
Among others, You need to be aware also of the pull-to-refresh and inertia (or momentum) of the smooth scroll behaviors.
Please, try to scroll or to swipe and look how the events are tracked: either the top bar or bottom bar will change color to display the user activity after reaching the bottom or the top respectively of the page.
JavaScript:
// get the button
var theBtn = document.getElementById('theBtn');
// get the box
var theBox = document.getElementById('theBox');
// add event to the button on click show/hide(toggle) the box
theBtn.addEventListener('click', () => {
theBox.classList.toggle('active');
});
// when scrolling on the box
theBox.onscroll = function(){
// get the top of the div
var theBoxTop = theBox.scrollTop;
if(theBoxTop <= 0){
// when it reaches 0 or less, hide the box. It'll toggle the class, since it's "show" will "hide"
theBox.classList.toggle('active');
}
};
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-size: 10px;
font-family: 'Arial', sans-serif;
height: 1500px;
}
html {
scroll-behavior: smooth;
}
ul {
list-style-type: none;
}
#theBox ul li {
border: 1px solid;
height: 100px;
}
#navbar-bottom {
height: 100px;
width: 100%;
background: rgb(90, 111, 143);
position: fixed;
bottom: 0;
left: 0;
right: 0;
box-shadow: 0 0 2px 2px rgba(90, 111, 143, 0.562);
display: flex;
justify-content: space-around;
align-items: center;
}
#theBox {
background-color: red;
height: 350px;
width: 100%;
position: fixed;
bottom: 0;
transform: translateY(100%);
transition: all 0.3s;
overflow-y: scroll;
}
#theBox.active{
transform: translateY(0);
}
.myBtns {
width: 50px;
height: 50px;
border-radius: 50%;
border: none;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
cursor: pointer;
}
.myBtns span {
height: 3px;
width: 30px;
background-color: black;
margin: 3px 0;
}
<main role="main">
<div id="theBox">
<ul>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
</ul>
</div>
<div id="navbar-bottom">
<button class="myBtns"></button>
<button class="myBtns" id="theBtn">
<span></span>
<span></span>
<span></span>
</button>
<button class="myBtns"></button>
</div>
</main>
jQuery:
// add event to the button on click show/hide(toggle) the box
$('#theBtn').click(function(){
$('#theBox').toggleClass('active');
});
// when scrolling on the box
$('#theBox').scroll(function () {
// get the top of the div
var theBoxTop = $('#theBox').scrollTop();
// when it reaches 0 or less, hide the box. It'll toggle the class, since it's "show" will "hide"
if(theBoxTop <= 0){
$('#theBox').toggleClass('active');
}
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-size: 10px;
font-family: 'Arial', sans-serif;
height: 1500px;
}
html {
scroll-behavior: smooth;
}
ul {
list-style-type: none;
}
#theBox ul li {
border: 1px solid;
height: 100px;
}
#navbar-bottom {
height: 100px;
width: 100%;
background: rgb(90, 111, 143);
position: fixed;
bottom: 0;
left: 0;
right: 0;
box-shadow: 0 0 2px 2px rgba(90, 111, 143, 0.562);
display: flex;
justify-content: space-around;
align-items: center;
}
#theBox {
background-color: red;
height: 350px;
width: 100%;
position: fixed;
bottom: 0;
transform: translateY(100%);
transition: all 0.3s;
overflow-y: scroll;
}
#theBox.active{
transform: translateY(0);
}
.myBtns {
width: 50px;
height: 50px;
border-radius: 50%;
border: none;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
cursor: pointer;
}
.myBtns span {
height: 3px;
width: 30px;
background-color: black;
margin: 3px 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<main role="main">
<div id="theBox">
<ul>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
<li><p>Text</p></li>
</ul>
</div>
<div id="navbar-bottom">
<button class="myBtns"></button>
<button class="myBtns" id="theBtn">
<span></span>
<span></span>
<span></span>
</button>
<button class="myBtns"></button>
</div>
</main>
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
alert("you are at the bottom of the page");
}
};
Link to demo: http://jsfiddle.net/5xpoe4yg/
There are two solutions for this. One is for touch devices and second for devices using mouse.
Using Wheel event
If target is a mouse device, then we will use following method:
document.onwheel = event => ScrollAction(event);
For more info on wheel event, please visit this link.
Touch Devices
If target is a touch device then following method will be useful:
document.ontouchcancel = event => TouchInterrupt(event);
document.ontouchend = event => FingerRemoved(event);
document.ontouchmove = event => FingerDragged(event);
document.ontouchstart = event => FingerPlaced(event);
For more info on touch events, please visit this link.
I think your problem fully is solved by this solution.
Your specific question is solveable by listening to the wheel event, although the result is not terribly precise. The wheel event often fires before the scroll event so this example will sometimes log negative scroll value on the first scroll up from the bottom of the page:
const content = document.querySelector('.content');
for (let i = 0; i < 50; i++) {
const p = document.createElement('p');
p.textContent = 'Content';
content.append(p);
};
content.addEventListener('wheel', e => {
const atBottom = content.scrollHeight - content.scrollTop === content.clientHeight;
if (atBottom) console.log(e.deltaY);
});
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
height: 100vh;
width: 100%;
}
.content {
overflow-y: scroll;
height: 100%;
}
<div class="content"></div>
As others have suggested, a better approach for your use case might instead be to have an overlay which you can trigger on click/touch and then scroll into view. One issue you might run into is that deeply nested scroll on web browsers can get real ugly real fast, without resorting to pure JS solutions which also have their own performance issues.
This is a popup that, when clicked on, opens and enables you to scroll. When it gets to the top of the page, it's header sticks.
var navbar = document.querySelector('.navbar'),
navheader = document.querySelector('.navheader');
// Toggle navbar
navheader.addEventListener('click', e => {
navbar.classList.toggle('open');
if (!navbar.classList.contains('open')) {
navbar.style.overflow = 'hidden';
document.body.style.overflow = '';
navbar.scrollTop = 0;
stickTop = false;
navbar.classList.remove('sticky');
navbar.style.top = '';
navbar.style.transition = '.2s';
setTimeout(() => {
navbar.style.transition = '';
}, 200);
}
else {
navbar.style.overflow = 'overlay';
navbar.style.transition = '.2s';
setTimeout(() => {
navbar.style.transition = '';
}, 200);
}
})
var prevtop = 0;
var stickTop = false;
// Add scroll listener
navbar.addEventListener('scroll', e => {
// If navbar is open
if (navbar.classList.contains('open')) {
if (!stickTop) {
navbar.style.top = navbar.getBoundingClientRect().top - navbar.scrollTop + 'px';
}
if ((window.innerHeight - navbar.getBoundingClientRect().bottom) >= 0) {
document.body.style.overflow = 'hidden';
navbar.style.overflow = 'auto';
navbar.style.top = 0;
navbar.classList.add('sticky');
stickTop = true;
}
if (navbar.scrollTop == 0) {
navbar.classList.remove('open');
navbar.style.overflow = 'hidden';
document.body.style.overflow = '';
stickTop = false;
navbar.classList.remove('sticky');
navbar.style.top = '';
navbar.style.transition = '.2s';
setTimeout(() => {
navbar.style.transition = '';
}, 200);
}
}
})
body {
font-family: sans-serif;
}
.navbar {
position: fixed;
top: calc(100vh - 50px);
height: 100vh;
left: 0;
width: 100%;
overflow: hidden;
}
.navbar.open {
top: 50vh;
}
.navcontent {
background: black;
width: 100%;
color: white;
}
.navcontent p {
margin: 0;
}
.navheader {
height: 50px;
width: 100%;
background: lightblue;
cursor: pointer;
top: 0;
position: sticky;
display: flex;
justify-content: center;
z-index: 1;
}
.navheader::before {
width: 50px;
height: 3px;
margin-top: 10px;
background: white;
border-radius: 3px;
content: '';
}
<div class="navbar">
<div class="navheader"></div>
<div class="navcontent"><p>S</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>E</p></div>
</div>
<div class="content">
<p>S</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>E</p>
</div>
Using the tags() function, there is a parameter for maximum number of tags (maxTags) you can enter into each input.
I need to hide(); the tags-input when maxTags = 2 and then
show(); when the tag is removed / not at the max.
maxTags :1 isn't working but it should be. Only 2 or more is acceptable. I tried debugging the max tags parameter but couldn't find out why maxTags: 1 is
unacceptable.
How can I change the function to allow maxTags: 1 and also show/hide the tag-input when maxTags is reached?
// max tags in tags() function:
if (opts.maxTags) {
if ($self.val().split(",").length == opts.maxTags) {
otherCheck = false;
$input.val("");
$next.val("");
}
}
// Calling the tags() function:
$("#" + formId)
.find(".input--proj")
.tags({
unique: true,
maxTags: 2
})
.autofill({
data: autolist
});
(function($) {
$.fn.tags = function(opts) {
var selector = this.selector;
//console.log("selector",selector);
// updates the original input
function update($original) {
var all = [];
var list = $original.closest(".tags-wrapper").find("li.tag span").each(function() {
all.push($(this).text());
});
all = all.join(",");
$original.val(all);
}
return this.each(function() {
var self = this,
$self = $(this),
$wrapper = $("<div class='tags-wrapper'><ul></ul></div");
tags = $self.val(),
tagsArray = tags.split(","),
$ul = $wrapper.find("ul");
// make sure have opts
if (!opts) opts = {};
opts.maxSize = 50;
// add tags to start
tagsArray.forEach(function(tag) {
if (tag) {
$ul.append("<li class='tag'><span>" + tag + "</span><a href='#'>x</a></li>");
}
});
// get classes on this element
if (opts.classList) $wrapper.addClass(opts.classList);
// add input
$ul.append("<li class='tags-input'><input type='text' class='tags-secret'/></li>");
// set to dom
$self.after($wrapper);
// add the old element
$wrapper.append($self);
// size the text
var $input = $ul.find("input"),
size = parseInt($input.css("font-size")) - 4;
// delete a tag
$wrapper.on("click", "li.tag a", function(e) {
e.preventDefault();
$(this).closest("li").remove();
$self.trigger("tagRemove", $(this).closest("li").find("span").text());
update($self);
$("[data-search]").keyup();
});
// backspace needs to check before keyup
$wrapper.on("keydown", "li input", function(e) {
// backspace
if (e.keyCode == 8 && !$input.val()) {
var $li = $ul.find("li.tag:last").remove();
update($self);
$self.trigger("tagRemove", $li.find("span").text());
}
// prevent for tab
if (e.keyCode == 9) {
e.preventDefault();
}
});
// as we type
$wrapper.on("keyup", "li input", function(e) {
e.preventDefault();
$ul = $wrapper.find("ul");
var $next = $input.next(),
usingAutoFill = $next.hasClass("autofill-bg"),
$inputLi = $ul.find("li.tags-input");
// regular size adjust
$input.width($input.val().length * (size));
// if combined with autofill, check the bg for size
if (usingAutoFill) {
$next.width($next.val().length * (size));
$input.width($next.val().length * (size));
// make sure autofill doesn't get too big
if ($next.width() < opts.maxSize) $next.width(opts.maxSize);
var list = $next.data().data;
}
// make sure we don't get too high
if ($input.width() < opts.maxSize) $input.width(opts.maxSize);
// tab, comma, enter
if (!!~[9, 188, 13].indexOf(e.keyCode)) {
var val = $input.val().replace(",", "");
var otherCheck = true;
// requring a tag to be in autofill
if (opts.requireData && usingAutoFill) {
if (!~list.indexOf(val)) {
otherCheck = false;
$input.val("");
}
}
// unique
if (opts.unique) {
// found a match already there
if (!!~$self.val().split(",").indexOf(val)) {
otherCheck = false;
$input.val("");
$next.val("");
}
}
// max tags
if (opts.maxTags) {
if ($self.val().split(",").length == opts.maxTags) {
otherCheck = false;
$input.val("");
$next.val("");
}
}
// if we have a value, and other checks pass, add the tag
if (val && otherCheck) {
// place the new tag
$inputLi.before("<li class='tag'><span>" + val + "</span><a href='#'>x</a></li>");
// clear the values
$input.val("");
if (usingAutoFill) $next.val("");
update($self);
$self.trigger("tagAdd", val);
}
}
});
});
}
})(jQuery);
var uniqueId = 1;
$(function() {
$(".btn--new").click(function() {
var copy = $("#s_item").clone(true, true);
var formId = "item_" + uniqueId;
copy.attr("id", formId);
$("#s_list").append(copy);
$("#" + formId)
.find(".input--proj")
.each(function() {
var autolist = new Array();
$.each($(".studio__project"), function(index, value) {
if ($.inArray($(value).attr("data-proj"), autolist) < 1) {
autolist.push($(value).attr("data-proj").toLowerCase());
}
});
$("#" + formId)
.find(".input--proj")
.tags({
unique: true,
maxTags: 2
})
.autofill({
data: autolist
});
function placeholderproj() {
$(".search__label--proj")
.find(".tags-secret")
.attr("placeholder", "Enter Keyword");
}
$(document).ready(placeholderproj);
});
uniqueId++;
});
});
$(document).on("keyup , keypress", "li input", function(e) {
$.each($(".tag"), function(index, value) {
$.each($(".studio__project"), function(subIndex, subValue) {
if (
$(subValue).attr("data-proj").toLowerCase() ==
$(value).find("span").html()
) {
var itemColor = $(subValue).attr("data-color");
$(value).css("background-color", itemColor);
}
});
});
$("[data-search]").keyup();
});
$(document).on("click", ".tag a", function(e) {
$("[data-search]").keyup();
});
$(document).ready(function() {
$(".btn--new").trigger("click");
$(".btn--new").trigger("click");
$(".btn--new").trigger("click");
});
::-webkit-input-placeholder {
/* Chrome/Opera/Safari */
color: #8e8e8e;
}
::-moz-placeholder {
/* Firefox 19+ */
color: #8e8e8e;
}
:-ms-input-placeholder {
/* IE 10+ */
color: #8e8e8e;
}
:-moz-placeholder {
/* Firefox 18- */
color: #8e8e8e;
}
.tags-wrapper {
background: white;
display: flex;
position: relative;
width: 100%;
height: 50px;
top: -1px;
border: 1px solid whitesmoke;
overflow: hidden;
}
.tags-wrapper ul {
position: absolute;
display: flex;
flex: 1;
align-items: center;
top: 0;
bottom: 0;
right: 0;
left: 0;
list-style-type: none;
margin: 0;
padding: 0;
}
.tags-wrapper li {
flex-grow: 1;
margin-left: 5px;
}
.tags-wrapper li input {
display: block;
border: none;
width: 100% !important;
}
.tags-wrapper li.tag {
display: flex;
flex-grow: 0;
position: relative;
padding: 10px;
font-size: 14px;
align-items: center;
border-radius: 5px;
list-style: none;
background-image: none;
box-shadow: none;
color: white;
}
.tags-wrapper li a {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
text-decoration: none;
color: rgba(0, 0, 0, 0);
}
.tags-wrapper li a:hover {
background-color: rgba(0, 0, 0, 0.4);
border-radius: 5px;
color: rgba(0, 0, 0);
background-image: url("http://svgshare.com/i/3yv.svg");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.tags-wrapper input {
display: none;
}
#s_item {
display: none
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.staticaly.com/gh/moofawsaw/donorport/master/auto-fill.min.js"></script>
<a id="btn_studio" href="#" class="btn btn--new ripple w-button">Create</a>
<div>keywords are: blue, red, green</div>
<div id="s_list">
<div id="s_item" data-item="studio" data-mode="none" class="post__item studio__item">
<div data-item="studio" class="post__itemwrap post__itemwrap--studio">
<div class="search search--proj w-embed"><label class="search__label--proj" data-color="">
<input type="text" class="input--proj" autocomplete="off" placeholder="">
</label></div>
<div class="w-dyn-list">
<div class="w-dyn-items">
<div class="w-dyn-item">
<div class="w-embed">
<div class="studio__project" data-proj="Blue" data-color="blue"></div>
</div>
</div>
<div class="w-dyn-item">
<div class="w-embed">
<div class="studio__project" data-proj="Green" data-color="green"></div>
</div>
</div>
<div class="w-dyn-item">
<div class="w-embed">
<div class="studio__project" data-proj="Red" data-color="red"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
I've added code to test maxTags value from 1 to 3. Just replace it.
Removed a tag. Show input.
$wrapper.on("click", "li.tag a", function(e) {
e.preventDefault();
$(this).closest("li").remove();
$self.trigger("tagRemove", $(this).closest("li").find("span").text());
update($self);
$("[data-search]").keyup();
$input.show(); // show input on remove
});
If string has no content, return 0. Else use split to find number of elements.
if (opts.maxTags) {
var len = $self.val();
len = len.trim().length > 0 ? len.split(',').length : 0;
(function($) {
$.fn.tags = function(opts) {
var selector = this.selector;
//console.log("selector",selector);
// updates the original input
function update($original) {
var all = [];
var list = $original.closest(".tags-wrapper").find("li.tag span").each(function() {
all.push($(this).text());
});
all = all.join(",");
$original.val(all);
}
return this.each(function() {
var self = this,
$self = $(this),
$wrapper = $("<div class='tags-wrapper'><ul></ul></div");
tags = $self.val(),
tagsArray = tags.split(","),
$ul = $wrapper.find("ul");
// make sure have opts
if (!opts) opts = {};
opts.maxSize = 50;
// add tags to start
tagsArray.forEach(function(tag) {
if (tag) {
$ul.append("<li class='tag'><span>" + tag + "</span><a href='#'>x</a></li>");
}
});
// get classes on this element
if (opts.classList) $wrapper.addClass(opts.classList);
// add input
$ul.append("<li class='tags-input'><input type='text' class='tags-secret'/></li>");
// set to dom
$self.after($wrapper);
// add the old element
$wrapper.append($self);
// size the text
var $input = $ul.find("input"),
size = parseInt($input.css("font-size")) - 4;
// delete a tag
$wrapper.on("click", "li.tag a", function(e) {
e.preventDefault();
$(this).closest("li").remove();
$self.trigger("tagRemove", $(this).closest("li").find("span").text());
update($self);
$("[data-search]").keyup();
$input.show(); // show input on remove
});
// backspace needs to check before keyup
$wrapper.on("keydown", "li input", function(e) {
// backspace
if (e.keyCode == 8 && !$input.val()) {
var $li = $ul.find("li.tag:last").remove();
update($self);
$self.trigger("tagRemove", $li.find("span").text());
}
// prevent for tab
if (e.keyCode == 9) {
e.preventDefault();
}
});
// as we type
$wrapper.on("keyup", "li input", function(e) {
e.preventDefault();
$ul = $wrapper.find("ul");
var $next = $input.next(),
usingAutoFill = $next.hasClass("autofill-bg"),
$inputLi = $ul.find("li.tags-input");
// regular size adjust
$input.width($input.val().length * (size));
// if combined with autofill, check the bg for size
if (usingAutoFill) {
$next.width($next.val().length * (size));
$input.width($next.val().length * (size));
// make sure autofill doesn't get too big
if ($next.width() < opts.maxSize) $next.width(opts.maxSize);
var list = $next.data().data;
}
// make sure we don't get too high
if ($input.width() < opts.maxSize) $input.width(opts.maxSize);
// tab, comma, enter
if (!!~[9, 188, 13].indexOf(e.keyCode)) {
var val = $input.val().replace(",", "");
var otherCheck = true;
// requring a tag to be in autofill
if (opts.requireData && usingAutoFill) {
if (!~list.indexOf(val)) {
otherCheck = false;
$input.val("");
}
}
// unique
if (opts.unique) {
// found a match already there
if (!!~$self.val().split(",").indexOf(val)) {
otherCheck = false;
$input.val("");
$next.val("");
}
}
// max tags
if (opts.maxTags) {
var len = $self.val();
len = len.trim().length > 0 ? len.split(',').length : 0;
if (len == opts.maxTags - 1) $input.hide();
if (len == opts.maxTags) {
otherCheck = false;
$input.val("");
$next.val("");
}
}
// if we have a value, and other checks pass, add the tag
if (val && otherCheck) {
// place the new tag
$inputLi.before("<li class='tag'><span>" + val + "</span><a href='#'>x</a></li>");
// clear the values
$input.val("");
if (usingAutoFill) $next.val("");
update($self);
$self.trigger("tagAdd", val);
}
}
});
});
}
})(jQuery);
var uniqueId = 1;
$(function() {
$(".btn--new").click(function() {
var copy = $("#s_item").clone(true, true);
var formId = "item_" + uniqueId;
copy.attr("id", formId);
$("#s_list").append(copy);
$("#" + formId)
.find(".input--proj")
.each(function() {
var autolist = new Array();
$.each($(".studio__project"), function(index, value) {
if ($.inArray($(value).attr("data-proj"), autolist) < 1) {
autolist.push($(value).attr("data-proj").toLowerCase());
}
});
$("#" + formId)
.find(".input--proj")
.tags({
unique: true,
maxTags: 1
})
.autofill({
data: autolist
});
function placeholderproj() {
$(".search__label--proj")
.find(".tags-secret")
.attr("placeholder", "Enter Keyword");
}
$(document).ready(placeholderproj);
});
uniqueId++;
});
});
$(document).on("keyup , keypress", "li input", function(e) {
$.each($(".tag"), function(index, value) {
$.each($(".studio__project"), function(subIndex, subValue) {
if (
$(subValue).attr("data-proj").toLowerCase() ==
$(value).find("span").html()
) {
var itemColor = $(subValue).attr("data-color");
$(value).css("background-color", itemColor);
}
});
});
$("[data-search]").keyup();
});
$(document).on("click", ".tag a", function(e) {
$("[data-search]").keyup();
});
$(document).ready(function() {
$(".btn--new").trigger("click");
$(".btn--new").trigger("click");
$(".btn--new").trigger("click");
});
::-webkit-input-placeholder {
/* Chrome/Opera/Safari */
color: #8e8e8e;
}
::-moz-placeholder {
/* Firefox 19+ */
color: #8e8e8e;
}
:-ms-input-placeholder {
/* IE 10+ */
color: #8e8e8e;
}
:-moz-placeholder {
/* Firefox 18- */
color: #8e8e8e;
}
.tags-wrapper {
background: white;
display: flex;
position: relative;
width: 100%;
height: 50px;
top: -1px;
border: 1px solid whitesmoke;
overflow: hidden;
}
.tags-wrapper ul {
position: absolute;
display: flex;
flex: 1;
align-items: center;
top: 0;
bottom: 0;
right: 0;
left: 0;
list-style-type: none;
margin: 0;
padding: 0;
}
.tags-wrapper li {
flex-grow: 1;
margin-left: 5px;
}
.tags-wrapper li input {
display: block;
border: none;
width: 100% !important;
}
.tags-wrapper li.tag {
display: flex;
flex-grow: 0;
position: relative;
padding: 10px;
font-size: 14px;
align-items: center;
border-radius: 5px;
list-style: none;
background-image: none;
box-shadow: none;
color: white;
}
.tags-wrapper li a {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
text-decoration: none;
color: rgba(0, 0, 0, 0);
}
.tags-wrapper li a:hover {
background-color: rgba(0, 0, 0, 0.4);
border-radius: 5px;
color: rgba(0, 0, 0);
background-image: url("http://svgshare.com/i/3yv.svg");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.tags-wrapper input {
display: none;
}
#s_item {
display: none
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.staticaly.com/gh/moofawsaw/donorport/master/auto-fill.min.js"></script>
<a id="btn_studio" href="#" class="btn btn--new ripple w-button">Create</a>
<div>keywords are: blue, red, green</div>
<div id="s_list">
<div id="s_item" data-item="studio" data-mode="none" class="post__item studio__item">
<div data-item="studio" class="post__itemwrap post__itemwrap--studio">
<div class="search search--proj w-embed"><label class="search__label--proj" data-color="">
<input type="text" class="input--proj" autocomplete="off" placeholder="">
</label></div>
<div class="w-dyn-list">
<div class="w-dyn-items">
<div class="w-dyn-item">
<div class="w-embed">
<div class="studio__project" data-proj="Blue" data-color="blue"></div>
</div>
</div>
<div class="w-dyn-item">
<div class="w-embed">
<div class="studio__project" data-proj="Green" data-color="green"></div>
</div>
</div>
<div class="w-dyn-item">
<div class="w-embed">
<div class="studio__project" data-proj="Red" data-color="red"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
I have a todo project with localStorage and SortableJS. I am having problem where when I sort my todo list, it won't update the localStorage. Can somebody help me figure out a way to save the sorted list? The code is below but would be nice to visit the codepen link under the snippet.
const clear = document.querySelector(".clear");
const dateElement = document.getElementById("date");
const list = document.getElementById("list");
const input = document.getElementById("input");
// Class names
const CHECK = "fa-check-circle";
const UNCHECK = "fa-circle-thin";
const LINE_THROUGH = "lineThrough";
// Variables
let LIST, id;
// Get item from localStorage
let data = localStorage.getItem("TODO");
// Check if data is not empty
if (data) {
LIST = JSON.parse(data);
id = LIST.length;
loadList(LIST);
} else {
LIST = [];
id = 0;
}
// Load items to the user's interface
function loadList(array) {
array.forEach(function(item) {
addToDo(item.name, item.id, item.done, item.trash);
});
}
// Clear the localStorage
clear.addEventListener("click", function() {
localStorage.clear();
location.reload();
})
// Show today's date
const options = {
weekday: "long",
month: "short",
day: "numeric"
};
const today = new Date();
dateElement.innerHTML = today.toLocaleDateString("en-US", options);
// Add to do function
function addToDo(toDo, id, done, trash) {
if (trash) {
return;
}
const DONE = done ? CHECK : UNCHECK;
const LINE = done ? LINE_THROUGH : "";
const item = `<li class="item">
<i class="fa ${DONE}" job="complete" id="${id}"></i>
<p class="text ${LINE}">${toDo}</p>
<i class="fa fa-trash-o de" job="delete" id="${id}"></i>
</li>
`;
const position = "beforeend";
list.insertAdjacentHTML(position, item);
}
// Add an item to the list when the user cick the enter key
document.addEventListener("keyup", function(event) {
if (event.keyCode == 13) {
const toDo = input.value;
// If the input isn't empty
if (toDo) {
addToDo(toDo);
LIST.push({
name: toDo,
id: id,
done: false,
trash: false
});
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
id++;
}
input.value = ""
}
});
// complete to do
function completeToDo(element) {
element.classList.toggle(CHECK);
element.classList.toggle(UNCHECK);
element.parentNode.querySelector(".text").classList.toggle(LINE_THROUGH);
LIST[element.id].done = LIST[element.id].done ? false : true;
}
// Remove to do
function removeToDo(element) {
element.parentNode.parentNode.removeChild(element.parentNode);
LIST[element.id].trash = true;
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
}
// Target the items created dynamically
list.addEventListener("click", function(event) {
const element = event.target;
const elementJob = element.attributes.job.value;
if (elementJob == "complete") {
completeToDo(element);
} else if (elementJob == "delete") {
removeToDo(element);
}
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
});
// For sorting the list
Sortable.create(list, {
animation: 100,
group: 'list-1',
draggable: '#list li',
handle: '#list li',
sort: true,
filter: '.sortable-disabled',
chosenClass: 'active'
});
/* ------------ youtube.com/CodeExplained ------------ */
body {
padding: 0;
margin: 0;
background-color: rgba(0, 0, 0, 0.1);
font-family: 'Titillium Web', sans-serif;
}
/* ------------ container ------------ */
.container {
padding: 10px;
width: 380px;
margin: 0 auto;
}
/* ------------ header ------------ */
.header {
width: 380px;
height: 200px;
background-image: url('');
background-size: 100% 200%;
background-repeat: no-repeat;
border-radius: 15px 15px 0 0;
position: relative;
}
.clear {
width: 30px;
height: 30px;
position: absolute;
right: 20px;
top: 20px;
}
.clear i {
font-size: 30px;
color: #FFF;
}
.clear i:hover {
cursor: pointer;
text-shadow: 1px 3px 5px #000;
transform: rotate(45deg);
}
#date {
position: absolute;
bottom: 10px;
left: 10px;
color: #FFF;
font-size: 25px;
font-family: 'Titillium Web', sans-serif;
}
/* ------------ content ------------ */
.content {
width: 380px;
height: 350px;
max-height: 350px;
background-color: #FFF;
overflow: auto;
}
.content::-webkit-scrollbar {
display: none;
}
.content ul {
padding: 0;
margin: 0;
}
.item {
width: 380px;
height: 45px;
min-height: 45px;
position: relative;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
list-style: none;
padding: 0;
margin: 0;
}
.item i.co {
position: absolute;
font-size: 25px;
padding-left: 5px;
left: 15px;
top: 10px;
}
.item i.co:hover {
cursor: pointer;
}
.fa-check-circle {
color: #6eb200;
}
.item p.text {
position: absolute;
padding: 0;
margin: 0;
font-size: 20px;
left: 50px;
top: 5px;
background-color: #FFF;
max-width: 285px;
}
.lineThrough {
text-decoration: line-through;
color: #ccc;
}
.item i.de {
position: absolute;
font-size: 25px;
right: 15px;
top: 10px;
}
.item i.de:hover {
color: #af0000;
cursor: pointer;
}
/* ------------ add item ------------ */
.add-to-do {
position: relative;
width: 360px;
height: 40px;
background-color: #FFF;
padding: 10px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.add-to-do i {
position: absolute;
font-size: 40px;
color: #4162f6;
}
.add-to-do input {
position: absolute;
left: 50px;
height: 35px;
width: 310px;
background-color: transparent;
border: none;
font-size: 20px;
padding-left: 10px;
}
.add-to-do input::-webkit-input-placeholder {
/* Chrome/Opera/Safari */
color: #4162f6;
font-family: 'Titillium Web', sans-serif;
font-size: 20px;
}
.add-to-do input::-moz-placeholder {
/* Firefox 19+ */
color: #4162f6;
font-family: 'Titillium Web', sans-serif;
font-size: 20px;
}
.add-to-do input:-ms-input-placeholder {
/* IE 10+ */
color: #4162f6;
font-family: 'Titillium Web', sans-serif;
font-size: 20px;
}
.add-to-do input:-moz-placeholder {
/* Firefox 18- */
color: #4162f6;
font-family: 'Titillium Web', sans-serif;
font-size: 20px;
}
<script src="https://kit.fontawesome.com/ed2e310181.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/RubaXa/Sortable/Sortable.min.js"></script>
<div class="container">
<div class="header">
<div class="clear">
<i class="fa fa-refresh"></i>
</div>
<div id="date"></div>
</div>
<div class="content">
<ul id="list">
<!-- <li class="item">
<i class="fa fa-circle-thin co" job="complete" id="0"></i>
<p class="text"></p>
<i class="fa fa-trash-o" job="delete" id="1"></i>
</li> -->
</ul>
</div>
<div class="add-to-do">
<i class="fa fa-plus-circle"></i>
<input type="text" id="input" placeholder="Add a to-do">
</div>
</div>
Please visit my codepen for a working project.
Try to add 2 or more todos then sort, on refresh was hoping to keep the sorted list.
https://codepen.io/Foxseiz/pen/ZEGadWZ
Sortable.create(list, {
group: "TODO2",
options: {
animation: 100,
draggable: "#list li",
handle: "#list li",
sort: true,
filter: ".sortable-disabled",
chosenClass: "active"
},
store: {
/**
* Get the order of elements. Called once during initialization.
* #param {Sortable} sortable
* #returns {Array}
*/
get: function(sortable) {
var order = localStorage.getItem(sortable.options.group.name);
return order ? order.split("|") : [];
},
/**
* Save the order of elements. Called onEnd (when the item is dropped).
* #param {Sortable} sortable
*/
set: function(sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.group.name, order.join("|"));
}
}
});
That would work in your case
For your Sortable.create option you can do the following:
// For sorting the list
Sortable.create(list, {
animation: 100,
group: 'list-1',
draggable: '#list li',
handle: '#list li',
sort: true,
filter: '.sortable-disabled',
chosenClass: 'active',
onSort: function(e) {
var items = e.to.children;
var result = [];
for (var i = 0; i < items.length; i++) {
result.push(items[i].id);
}
var lsBefore = JSON.parse(localStorage.getItem("TODO"));
var lsAfter = [];
for (var i = 0; i < result.length; i++) {
var found = false;
for (var j = 0; j < lsBefore.length && !found; j++) {
if (lsBefore[j].id == result[i]) {
lsAfter.push(lsBefore[j]);
lsBefore.splice(j, 1);
found = true;
}
}
}
localStorage.setItem("TODO", JSON.stringify(lsAfter));
console.log(result);
console.log(lsBefore);
console.log(lsAfter);
}
The lsAfter is your re-sorted set of objects that you can store/update in local storage.
My solution also requires that your const item looks like this (I added the id attribute to the <li> element:
const item = `<li class="item" id="${id}">
<i class="fa ${DONE}" job="complete" id="${id}"></i>
<p class="text ${LINE}">${toDo}</p>
<i class="fa fa-trash-o de" job="delete" id="${id}"></i>
</li>
`;
You need use onSort callback.
example code:
const clear = document.querySelector(".clear");
const dateElement = document.getElementById("date");
const list = document.getElementById("list");
const input = document.getElementById("input");
// Class names
const CHECK = "fa-check-circle";
const UNCHECK = "fa-circle-thin";
const LINE_THROUGH = "lineThrough";
// Variables
let LIST, id;
// Get item from localStorage
let data = localStorage.getItem("TODO");
// Check if data is not empty
if(data) {
LIST = JSON.parse(data);
id = LIST.length;
loadList(LIST);
}else{
LIST =[];
id = 0;
}
// Load items to the user's interface
function loadList(array) {
array.forEach(function(item){
addToDo(item.name, item.id, item.done, item.trash);
});
}
// Clear the localStorage
clear.addEventListener("click", function() {
localStorage.clear();
location.reload();
})
// Show today's date
const options = {weekday : "long", month : "short", day : "numeric"};
const today = new Date();
dateElement.innerHTML = today.toLocaleDateString("en-US", options);
// Add to do function
function addToDo(toDo, id, done, trash) {
if(trash) { return; }
const DONE = done ? CHECK : UNCHECK;
const LINE = done ? LINE_THROUGH : "";
const item = `<li class="item">
<i class="fa ${DONE}" job="complete" id="${id}"></i>
<p class="text ${LINE}">${toDo}</p>
<i class="fa fa-trash-o de" job="delete" id="${id}"></i>
</li>
`;
const position = "beforeend";
list.insertAdjacentHTML(position, item);
}
// Add an item to the list when the user cick the enter key
document.addEventListener("keyup", function(event) {
if(event.keyCode == 13) {
const toDo = input.value;
// If the input isn't empty
if(toDo) {
addToDo(toDo);
LIST.push({
name : toDo,
id : id,
done : false,
trash : false
});
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
id++;
}
input.value = ""
}
});
// complete to do
function completeToDo(element) {
element.classList.toggle(CHECK);
element.classList.toggle(UNCHECK);
element.parentNode.querySelector(".text").classList.toggle(LINE_THROUGH);
LIST[element.id].done = LIST[element.id].done ? false : true;
}
// Remove to do
function removeToDo(element) {
element.parentNode.parentNode.removeChild(element.parentNode);
LIST[element.id].trash = true;
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
}
// Target the items created dynamically
list.addEventListener("click", function(event) {
const element = event.target;
const elementJob = element.attributes.job.value;
if(elementJob == "complete") {
completeToDo(element);
}else if(elementJob == "delete"){
removeToDo(element);
}
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
});
function swapArrayElements(arr, indexA, indexB) {
var temp = arr[indexA];
arr[indexA] = arr[indexB];
arr[indexB] = temp;
};
function orderList(oldIndex, newIndex) {
swapArrayElements(LIST, oldIndex, newIndex)
localStorage.setItem("TODO", JSON.stringify(LIST));
}
// For sorting the list
Sortable.create(list, {
animation: 100,
group: 'list-1',
draggable: '#list li',
handle: '#list li',
sort: true,
filter: '.sortable-disabled',
chosenClass: 'active',
onSort: function (/**Event*/evt) {
orderList(evt.oldIndex, evt.newIndex);
},
});
When you call
Sortable.create(list, {
animation: 100,
group: 'list-1',
draggable: '#list li',
handle: '#list li',
sort: true,
filter: '.sortable-disabled',
chosenClass: 'active'
});
There is actually a store option you can add. Like this:
Sortable.create(list, {
store: {
//Get the order of elements. Called once during initialization.
// #param {Sortable} sortable
// #returns {Array}
get: function (sortable) {
var order = localStorage.getItem(sortable.options.group.name);
return order ? order.split('|') : [];
},
// Save the order of elements.
// #param {Sortable} sortable
set: function (sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.group.name, order.join('|'));
}
},
...rest of your options
});
Also Sortable.create returns a "Sortable" object for your list so building on the code you have above you now have access to the Sortable object
var mySortable = Sortable.create(list, {...your options});
Now you can call mySortable.Save() after any event and your store's set function will get called. For example put mysortable.Save() in your document.addEventListener("keyup") function
The problem I encountered is I can't get any results from the jQuery UI Autocomplete form because of the variable scope. Let me show you.
// TAKE A CLOSE LOOK AT THIS METHOD
select: function(e, ui) {
$('#instant-search').text(ui.item.label);
$("#search").autocomplete("option", "source",
function(request, response) {
getAutocompleteResults(function(d) {
// DOESN'T WORK response(d);
});
// WORKS BUT IT SHOULD BE A DYNAMIC ARRAY FROM THE "D" OBJECT
// response(["anarchism", "anarchist black cross", "black rose (symbolism)", "communist symbolism", "political symbolism"]);
});
$("#search").autocomplete("search", ui.item.label);
In order to return results I have to use a function response([...]); outside the getAutocompleteResults(function(d) { ... }); function.
However, the source should be dynamic and not like the static array. In other words:
The function response(d); should return an object, which contains a few properties (title, value, extract). I have to access them by using response(d);, however, this function doesn't work inside getAutocompleteResults(function(d) { ... }); function. How can I achieve this?
There is a small snippet of code, however, the main problem is the select method. You can find this in the middle of the whole code block. I commented it out.
$(function() {
$("html").removeClass("no-js");
var autocompleteResults = [{
title: [],
extract: [],
pageId: []
}];
var capitalizeFirstLetter = function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
var changeText2 = function(e) {
var request = $("input").val() + String.fromCharCode(e.which);
$("#instant-search").text(request);
var getAutocompleteResults = function(callback) {
$.ajax({
url: "https://en.wikipedia.org/w/api.php?format=json&action=query&generator=search&gsrlimit=6&prop=extracts&origin=*&pilimit=max&exintro&explaintext&exsentences=1&gsrsearch=" +
$("#instant-search").text(),
beforeSend: function() {
$(".loading").show();
},
success: function(d) {
$(".loading").hide();
autocompleteResults[0].title = [];
autocompleteResults[0].extract = [];
autocompleteResults[0].pageId = [];
if (d.hasOwnProperty("query")) {
if (d.query.hasOwnProperty("pages")) {
$.each(d.query.pages, function(i) {
autocompleteResults[0].title.push(d.query.pages[i].title);
autocompleteResults[0].extract.push(d.query.pages[i].extract);
autocompleteResults[0].pageId.push(d.query.pages[i].pageid);
});
}
}
if (!autocompleteResults[0].length) {
$(".ui-autocomplete").hide();
}
autocompleteResults[0].title.sort(function(a, b) {
var nameA = a.toUpperCase();
var nameB = b.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
});
autocompleteResults[0].title = autocompleteResults[0].title.map(
function(i) {
return i.toLowerCase();
}
);
callback(autocompleteResults[0]);
},
datatype: "json",
cache: false
});
};
$("#search").autocomplete({
source: function(request, response) {
getAutocompleteResults(function(d) {
var results = [],
filteredAutocompleteResults = [];
filteredAutocompleteResults = d.title.filter(function(i) {
return (
i !=
$("#instant-search")
.text()
.toLowerCase()
);
});
for (var i = 0; i < d.title.length; i++) {
results[i] = {
label: filteredAutocompleteResults[i],
extract: d.extract[i],
pageId: d.pageId[i]
};
}
if (results.length == 5) {
response(results);
} else {
response(results.slice(0, 5));
}
});
},
response: function() {
if ($("#instant-search").text()) {
$("table").css("display", "table");
$(".wikisearch-container").css("margin-top", 100);
}
},
close: function() {
if (!$(".ui-autocomplete").is(":visible")) {
$(".ui-autocomplete").show();
}
},
appendTo: ".input",
focus: function(e) {
e.preventDefault();
},
delay: 0,
// TAKE A CLOSE LOOK AT THIS METHOD
select: function(e, ui) {
$('#instant-search').text(ui.item.label);
$("#search").autocomplete("option", "source",
function(request, response) {
getAutocompleteResults(function(d) {
// DOESN'T WORK response(d);
});
// WORKS BUT IT SHOULD BE A DYNAMIC ARRAY FROM THE "D" OBJECT
// response(["anarchism", "anarchist black cross", "black rose (symbolism)", "communist symbolism", "political symbolism"]);
});
$("#search").autocomplete("search", ui.item.label);
// EVERYTHING SHOULD BE FINE BELOW THIS LINE
if ($(".search-results").css("opacity") != 1) {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
"https://en.wikipedia.org/?curid=" + ui.item.pageId
);
$(".search-results").css("opacity", 1);
} else if (
$(".search-results h4")
.text()
.toLowerCase() != ui.item.label
) {
$(".search-results").css("opacity", 0);
setTimeout(function() {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
"https://en.wikipedia.org/?curid=" + ui.item.pageId
);
$(".search-results").css("opacity", 1);
}, 500);
}
},
create: function() {
$(this).data("ui-autocomplete")._renderItem = function(ul, item) {
return $("<li>")
.append(
'<div class="ui-menu-item-wrapper"><div class="autocomplete-first-field"><i class="fa fa-search" aria-hidden="true"></i></div><div class="autocomplete-second-field three-dots">' +
item.label +
"</div></div>"
)
.appendTo(ul);
};
}
});
};
var changeText1 = function(e) {
if (
/[-a-z0-90áãâäàéêëèíîïìóõôöòúûüùçñ!##$%^&*()_+|~=`{}\[\]:";'<>?,.\s\/]+/gi.test(
String.fromCharCode(e.which)
)
) {
$("input").on("keypress", changeText2);
}
// DONT TOUCH THIS AREA, IT HAS NOTHING TO DO WITH THE PROBLEM
var getInputSelection = function(input) {
var start = 0,
end = 0;
input.focus();
if (
typeof input.selectionStart == "number" &&
typeof input.selectionEnd == "number"
) {
start = input.selectionStart;
end = input.selectionEnd;
} else if (document.selection && document.selection.createRange) {
var range = document.selection.createRange();
if (range) {
var inputRange = input.createTextRange();
var workingRange = inputRange.duplicate();
var bookmark = range.getBookmark();
inputRange.moveToBookmark(bookmark);
workingRange.setEndPoint("EndToEnd", inputRange);
end = workingRange.text.length;
workingRange.setEndPoint("EndToStart", inputRange);
start = workingRange.text.length;
}
}
return {
start: start,
end: end,
length: end - start
};
};
switch (e.key) {
case "Backspace":
case "Delete":
e = e || window.event;
var keyCode = e.keyCode;
var deleteKey = keyCode == 46;
var sel, deletedText, val;
val = this.value;
sel = getInputSelection(this);
if (sel.length) {
// 0 kai paprastai trini po viena o 1 ar daugiau kai select su pele trini
$("#instant-search").text(
val.substr(0, sel.start) + val.substr(sel.end)
);
} else {
$("#instant-search").text(
val.substr(0, deleteKey ? sel.start : sel.start - 1) +
val.substr(deleteKey ? sel.end + 1 : sel.end)
);
}
break;
case "Enter":
if ($("#instant-search").text()) {
console.log("Redirecting...");
}
break;
}
if (!$("#instant-search").text()) {
$("table, .ui-autocomplete").hide();
$(".wikisearch-container").css("margin-top", "");
}
if (
$(".ui-menu-item-wrapper").hasClass("ui-state-active") &&
(e.key == "ArrowRight" || e.key == "ArrowLeft")
) {
$(".ui-autocomplete").autocomplete(""); // Error metas console ir taip neturėtų būti bet nežinau kaip padaryti kad pasirinkus elementą su <-- ar --> nepadarytų tik vieno rezultato todėl paliekam laikinai ;)
}
};
$("input").on("keydown", changeText1);
$("input").on("input", function(e) {
$("#instant-search").text($("#search").val());
});
});
html,
body {
height: 100%;
width: 100%;
}
body {
margin: 0;
padding: 0;
font-family: sans-serif;
background-image: url("http://www.part.lt/img/96816a00ec1fb87adc4ca8a04365b2b5719.jpg");
background-size: cover;
background-position: 100%;
}
.v-container {
display: table;
height: 100%;
width: 100%;
}
.v-content {
display: table-cell;
vertical-align: middle;
}
.text-center {
text-align: center;
}
.input {
overflow: hidden;
white-space: nowrap;
}
.input input#search {
width: calc(100% - 30px);
height: 50px;
border: none;
font-size: 10pt;
float: left;
color: #4f5b66;
padding: 0 15px;
outline: none;
}
.ui-autocomplete {
list-style: none;
background-color: #fff;
-webkit-user-select: none;
user-select: none;
padding: 0;
margin: 0;
width: 100% !important;
top: auto !important;
display: table;
table-layout: fixed;
}
.ui-helper-hidden-accessible {
display: none;
}
.autocomplete-first-field {
width: 15%;
display: inline-block;
}
.autocomplete-second-field {
width: 85%;
display: inline-block;
text-align: left;
vertical-align: middle;
}
.three-dots {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
table {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
display: none;
table-layout: fixed;
}
table tr {
background-color: #fff;
-webkit-user-select: none;
user-select: none;
}
tr:first-child {
background-color: #ffc800;
color: #fff;
}
table td,
.ui-menu-item-wrapper {
padding: 10px 0;
}
td:nth-child(2) {
width: 85%;
text-align: left;
}
.ui-menu-item,
table {
cursor: pointer;
}
:focus {
outline: 0;
}
a {
text-decoration: none;
color: #fff;
position: relative;
}
a:before {
content: "";
position: absolute;
width: 100%;
height: 0.0625rem;
bottom: 0;
left: 0;
background-color: #fff;
visibility: hidden;
-webkit-transform: scaleX(0);
transform: scaleX(0);
-webkit-transition: all 0.3s ease-in-out 0s;
transition: all 0.3s ease-in-out 0s;
}
a:hover:before {
visibility: visible;
-webkit-transform: scaleX(1);
transform: scaleX(1);
}
.search-results {
background: #fff;
margin-top: 50px;
border-left: 5px solid #0ebeff;
opacity: 0;
-webkit-transition: opacity 1s;
transition: opacity 1s;
}
.search-results h4,
.search-results p {
margin: 0;
padding: 10px;
text-align: left;
}
.search-results a {
color: #0ebeff;
display: inline-block;
margin: 1rem 0;
}
.search-results a:before {
background-color: #0ebeff;
}
.wikisearch-container {
width: 65%;
margin: 0 auto;
}
/* Loading animation */
#keyframes lds-eclipse {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
50% {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
#-webkit-keyframes lds-eclipse {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
50% {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.loading {
position: relative;
top: 9.5px;
right: 15px;
pointer-events: none;
display: none;
}
.lds-eclipse {
-webkit-animation: lds-eclipse 1s linear infinite;
animation: lds-eclipse 1s linear infinite;
width: 2rem;
height: 2rem;
border-radius: 50%;
margin-left: auto;
box-shadow: 0.08rem 0 0 #0ebeff;
}
#media (max-width: 71.875em) {
.wikisearch-container {
width: 75%;
}
}
#media (max-width: 50em) {
.wikisearch-container {
width: 85%;
}
}
#media (max-width: 17.96875em) {
.wikisearch-container {
width: 100%;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<html class="no-js">
<div class="v-container">
<div class="v-content text-center">
<div class="wikisearch-container">
<div class="input">
<input type="text" id="search" placeholder="Search...">
<div class="loading">
<div class="lds-eclipse"></div>
</div>
<button class="icon"><i class="fa fa-search"></i></button>
<table>
<tr>
<td class="fa fa-search">
<td id="instant-search" class="three-dots"></td>
</tr>
</table>
</div>
<div class="search-results">
<h4></h4>
<p></p>
<a target="_blank">Click here for more</a>
</div>
</div>
</div>
</div>
EDIT 1
After some changes, the results are shown, however, before the ajax call. How can I use response() only after the ajax was successfully completed (tried using success callback, didn't work :()?
Full project code: https://codepen.io/Kestis500/pen/zRONyw?editors=0010.
Here you can see step by step how it looks like:
How it looks like when you just reloaded the page:
Let's try entering "a":
We've got some results. Ok, let's try to click on the "anarchist symbolism" element:
Results should look like "anarchist symbolism" search. However, it returns the result of the "a" search. What if we pressed "fraktur" element?
Now it shows our previous search "anarchist symbolism" results. However, it should return elements of the "fraktur" search.
EDIT 2
I've fixed many things and removed some really non sense things from my code. However, the situation with the ajax call is still the same.
https://codepen.io/Kestis500/pen/pazppP?editors=0110
Any ideas?
EDIT 3
Fixed ajax lag (now the ajax request will be sent only after the previous ajax call).
https://codepen.io/Kestis500/pen/JpPLON?editors=0110
I had to first fix a few things in your Ajax call. We then collect the results and build an array that should be returned to response(). This will populate the AutoComplete.
First we will examine the HTML. There was some closing tags missing.
HTML
<div class="v-container">
<div class="v-content text-center">
<div class="wikisearch-container">
<div class="input ui-front">
<input type="text" id="search" placeholder="Search...">
<div class="loading">
<div class="lds-eclipse"></div>
</div>
<button class="icon">
<i class="fa fa-search"></i>
</button>
<table>
<tr>
<td class="fa fa-search"></td>
<td id="instant-search" class="three-dots"></td>
</tr>
</table>
</div>
<div class="search-results">
<h4></h4>
<p></p>
<a target="_blank">Click here for more</a>
</div>
</div>
</div>
</div>
You can see the table and it's cells all have the proper closing tags now.
I didn't make any changes to your CSS or Style.
JavaScript
$(function() {
var capitalizeFirstLetter = function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
$("#search").autocomplete({
source: function(request, response) {
var results = [];
$.ajax({
url: "https://en.wikipedia.org/w/api.php",
data: {
format: "json",
action: "query",
generator: "search",
gsrlimit: 6,
prop: "extracts|pageimages",
origin: "*",
pilimit: "max",
exintro: false,
explaintext: false,
exsentences: 1,
gsrsearch: request.term
},
beforeSend: function() {
$(".loading").show();
},
success: function(d) {
$(".loading").hide();
if (d.query.pages) {
$.each(d.query.pages, function(k, v) {
console.log(k, v.title, v.extract, v.pageid);
results.push({
label: v.title,
value: "https://en.wikipedia.org/?curid=" + v.pageid,
title: v.title,
extract: v.extract,
pageId: v.pageid
});
});
response(results);
}
},
datatype: "json",
cache: false
});
response(results);
},
close: function() {
if (!$(".ui-autocomplete").is(":visible")) {
$(".ui-autocomplete").show();
}
},
focus: function(e) {
e.preventDefault();
return false;
},
delay: 0,
select: function(e, ui) {
if ($(".search-results").css("opacity") != 1) {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
ui.item.value
);
$(".search-results").css("opacity", 1);
} else if (
$(".search-results h4")
.text()
.toLowerCase() != ui.item.label
) {
$(".search-results").css("opacity", 0);
setTimeout(function() {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
ui.item.value
);
$(".search-results").css("opacity", 1);
}, 500);
}
return false;
}
}).autocomplete("instance")._renderItem = function(ul, item) {
var $item = $("<li>");
var $wrap = $("<div>").appendTo($item);
var $field1 = $("<div>", {
class: "autocomplete-first-field"
}).appendTo($wrap);
$("<i>", {
class: "fa fa-search",
"aria-hidden": true
}).appendTo($field1);
$("<div>", {
class: "autocomplete-second-field three-dots"
}).html(item.label).appendTo($wrap);
return $item.appendTo(ul);
};
});
There was a lot of things to fix and improve.
Let's start with the Ajax. You're making a call to a MediaWiki API and expecting some results. When the call would come back, it would generate warnings about pilimit. Digging into the API docs, this is a parameter specific to the pageimages properties call. To fix this, the prop value had to be extracts|pageimages. Now I get a clean set of results.
You can see I broke out the data so that I could more easily make changes and see what parameters I was sending to the API. Nothing wrong with your method, I just find this a lot easier to work with.
This is all happening inside .autocomplete() when we are populating the source. When we use function as a source, it has to follow a few guidelines:
we pass a request and response in
results must be in an array
the array can contain objects, as long as they contain at least { label, value }
our results array must be passed to response function.
A brief example:
$(selector).autocomplete({
source: function(req, resp){
var q = req.term;
// The Request is an object that contains 1 index: term
// request.term will contain the content of our search
var results = [];
// An array to store the results
$.getJSON("myapi.php", {query: q}, function(data){
$.each(data, function(key, val){
// iterate over the result data and populate our result array
results.push({
label: data.name,
value: data.url
});
resp(results);
});
});
}
});
You can sort or filter the results all you like; as long as you pass them to response in the end.
With your focus and select callbacks, you want to return false. This is discussed more here: http://jqueryui.com/autocomplete/#custom-data
We also see a good example of rendering the menu item. I switched over to making jQuery objects versus raw HTML. You do what works best for you.
Working Example: https://jsfiddle.net/Twisty/vr6gv2aw/4/
Hope this helps.
I have a web app with a number of textareas and the ability to add more if you wish.
When you shift focus from one textarea to another, the one in focus animates to a larger size, and the rest shrink down.
When the page loads it handles the animation perfectly for the initial four boxes in the html file, but when you click on the button to add more textareas the animation fails to accomodate these new elements... that is, unless you place the initial queries in a function, and call that function from the addelement function tied to the button.
But!, when you do this it queries as many times as you add a new element. So, if you quickly add, say 10, new textareas, the next time you lay focus on any textarea the query runs 10 times.
Is the issue in my design, or jQueries implementation? If the former, how better can I design it, if it is the latter, how can I work around it?
I've tried to chop the code down to the relevant bits... I've tried everything from focus and blur, to keypresses, the latest is on click.
html::
<html>
<head>
<link rel="stylesheet" type="text/css" href="./sty/sty.css" />
<script src="./jquery.js"></script>
<script>
$().ready(function() {
var $scrollingDiv = $("#scrollingDiv");
$(window).scroll(function(){
$scrollingDiv
.stop()
//.animate({"marginTop": ($(window).scrollTop() + 30) + "px"}, "slow" );
.animate({"marginTop": ($(window).scrollTop() + 30) + "px"}, "fast" );
});
});
</script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>boxdforstacks</title>
</head>
<body>
<div class="grid">
<div class="col-left" id="left">
<div class="module" id="scrollingDiv">
<input type="button" value="add" onclick="addele()" />
<input type="button" value="rem" onclick="remele()" />
<p class="display">The value of the text input is: </p>
</div>
</div> <!--div class="col-left"-->
<div class="col-midd">
<div class="module" id="top">
<p>boxa</p>
<textarea class="tecksd" placeholder="begin typing here..." id="boxa" ></textarea>
<p>boxb</p>
<textarea class="tecksd" placeholder="begin typing here..." id="boxb"></textarea>
<p>boxc</p>
<textarea class="tecksd" placeholder="begin typing here..." id="boxc"></textarea>
<p>boxd</p>
<textarea class="tecksd" placeholder="begin typing here..." id="boxd"></textarea>
</div>
</div> <!--div class="col-midd"-->
</div> <!--div class="grid"-->
</body>
</html>
<script type="text/javascript" src="boxd.js"></script>
js:
function onit(){
$('textarea').on('keyup change', function() {
$('p.display').text('The value of the text input is: ' + $(this).val());
});
}
$('textarea').on("click",function(){
//alert(this.id.substring(0,3));
if ( this.id.substring(0,3) == 'box' ){
$('textarea').animate({ height: "51" }, 1000);
$(this).animate({ height: "409" }, 1000);
} else {
$('textarea').animate({ height: "51" }, 1000);
}
}
);
var boxfoc="";
var olebox="";
var numb = 0;
onit();
function addele() {
var tops = document.getElementById('top');
var num = numb + 1;
var romu = romanise(num);
var newbox = document.createElement('textarea');
var newboxid = 'box'+num;
newbox.setAttribute('id',newboxid);
newbox.setAttribute('class','tecksd');
newbox.setAttribute('placeholder','('+romu+')');
tops.appendChild(newbox);
numb = num;
onit();
} //addele(), add element
function remele(){
var tops = document.getElementById('top');
var boxdone = document.getElementById(boxfoc);
tops.removeChild(boxdone);
} // remele(), remove element
function romanise (num) {
if (!+num)
return false;
var digits = String(+num).split(""),
key = ["","c","cc","ccc","cd","d","dc","dcc","dccc","cm",
"","x","xx","xxx","xl","l","lx","lxx","lxxx","xc",
"","i","ii","iii","iv","v","vi","vii","viii","ix"],
roman = "",
i = 3;
while (i--)
roman = (key[+digits.pop() + (i * 10)] || "") + roman;
return Array(+digits.join("") + 1).join("M") + roman;
} // romanise(), turn numbers into roman numerals
css :
.tecksd {
width: 97%;
height: 51;
resize: none;
outline: none;
border: none;
font-family: "Lucida Console", Monaco, monospace;
font-weight: 100;
font-size: 70%;
background: white;
/* box-shadow: 1px 2px 7px 1px #0044FF;*/
}
.tecksded {
width: 97%;
resize: none;
outline: none;
border: none;
overflow: auto;
position: relative;
font-family: "Lucida Console", Monaco, monospace;
font-weight: 100;
font-size: 70%;
background: white;
/* box-shadow: 1px 2px 7px #FFDD00;*/
}
/*#postcomp {
width: 500px;
}*/
* {
#include box-sizing(border-box);
}
$pad: 20px;
.grid {
background: white;
margin: 0 0 $pad 0;
&:after {
/* Or #extend clearfix */
content: "";
display: table;
clear: both;
}
}
[class*='col-'] {
float: left;
padding-right: $pad;
.grid &:last-of-type {
padding-right: 0;
}
}
.col-left {
width: 13%;
}
.col-midd {
width: 43%;
}
.col-rght {
width: 43%;
}
.module {
padding: $pad;
}
/* Opt-in outside padding */
.grid-pad {
padding: $pad 0 $pad $pad;
[class*='col-']:last-of-type {
padding-right: $pad;
}
}
body {
padding: 10px 50px 200px;
background: #FFFFFF;
background-image: url('./backgrid.png');
}
h1 {
color: black;
font-size: 11px;
font-family: "Lucida Console", Monaco, monospace;
font-weight: 100;
}
p {
color: white;
font-size: 11px;
font-family: "Lucida Console", Monaco, monospace;
font-weight: 100;
}
You should use the following:
// New way (jQuery 1.7+) - .on(events, selector, handler)
$(document).on("click", "textarea", function () {
event.preventDefault();
alert('testlink');
});
Since the textarea is added dynamically, you need to use event delegation to register the event handler.
Try
$(document).on('click', 'textarea', function() {
// do something
});
The issue is you are binding the textareas only on the page load. I made a JSFiddle with working code: http://jsfiddle.net/VpABC/
Here's what I changed:
I wrapped:
$('textarea').on("click", function () {
//alert(this.id.substring(0,3));
if (this.id.substring(0, 3) == 'box') {
$('textarea').animate({
height: "51"
}, 1000);
$(this).animate({
height: "409"
}, 1000);
} else {
$('textarea').animate({
height: "51"
}, 1000);
}
});
in a function so it looked like this:
function bindTextAreas() {
$('textarea').unbind("click");
$('textarea').on("click", function () {
//alert(this.id.substring(0,3));
if (this.id.substring(0, 3) == 'box') {
$('textarea').animate({
height: "51"
}, 1000);
$(this).animate({
height: "409"
}, 1000);
} else {
$('textarea').animate({
height: "51"
}, 1000);
}
});
}
bindTextAreas();
What this does is it allows you to call this function, bindTextAreas, whenever you create a new textarea. This will unbind all the current events than rebind them. This will make it so your new textarea is has the click handler setup.
An place where this function is called is in the addele function like this:
function addele() {
var tops = document.getElementById('top');
var num = numb + 1;
var romu = romanise(num);
var newbox = document.createElement('textarea');
var newboxid = 'box' + num;
newbox.setAttribute('id', newboxid);
newbox.setAttribute('class', 'tecksd');
newbox.setAttribute('placeholder', '(' + romu + ')');
tops.appendChild(newbox);
numb = num;
onit();
bindTextAreas();
} //addele(), add element
Notice the bindTextAreas(); line near the bottom. This reloads all the click handlers.