Html5 draggable en droppable - javascript

I try to add some drag & drop functionality to a Laravel Livewire project. I used jQuery ui in the past, but I prefer not to combine livewire/Alpine js/jQuery...
The context: There are a number of workshops and every workshops has students. The admin should be able to drag students from one workshop to another, after which a Livewire function is fired to update the database.
Some simplified html:
#foreach($workshops as $workshop)
<div ondrop="drop(event)" ondragover="allowDrop(event)" class="drop">
#foreach($workshop->students as $student)
<div draggable="true" ondragstart="drag(event)" id="{{$gebruiker->id}}" data-event="{{$item->id}}">{{$student->name}}</div>
#endforeach
</div>
#endforeach
The javascript
<script>
var studentId;
function drag(event) {
studentId = event.target.id;
event.dataTransfer.setData("text", event.target.id);
event.target.style.opacity = 0;
}
function drop(ev) {
//alert('dropped');
ev.preventDefault();
var studentId = ev.dataTransfer.getData("text");
var targetWorkshopId = ev.target.closest(".drop").id;
var sourceWorkshopId = document.getElementById(studentId).getAttribute('data-event');
if (targetWorkshopId == sourceWorkshopId) {
document.getElementById(studentId).style.opacity = 1;
return;
} else {
Livewire.emit('studentDropped', studentId, sourceWorkshopId, targetWorkshopId);
document.getElementById(sourceWorkshopId).querySelector("#" + studentId).style.opacity = 0;
document.getElementById(targetWorkshopId).querySelector("#" + studentId).style.opacity = 100;
}
}
</script>
Works ok. When the dragging starts, the student's opacity in de sourceWorkshop goes to zero. When a student is dropped to another workshop, the Livewire method is fired.
The only thing that I would like to add (and don't succeed in) is: when a student is dragged to an element that is not droppable, I want him to return to het sourceWorkshop, and I want his opacity to be set at 100 again. That is not the case now. The opacity is set to 0 in the drag method.
How and where to fire a function when the student is dropped to an element that is not droppable?

Related

JS window.addEventListener Only Works On One Page

I have a JavaScript if element is on screen function which is triggered by a window.addEventListener. When a specific div is in view then CSS classes are added (from Animate CSS) to a div element. The code only works on one page of my website and not on the others.
I get this console error only on the second page (where the JS doesn't work)
TypeError: $elem.offset(...) is undefined
Live Project URL:
// Element In View function
function isOnScreen(elem) {
// if the element doesn't exist, abort
if (elem.length == 0) {
return;
}
var $window = jQuery(window)
var viewport_top = $window.scrollTop()
var viewport_height = $window.height()
var viewport_bottom = viewport_top + viewport_height
var $elem = jQuery(elem)
var top = $elem.offset().top
var height = $elem.height()
var bottom = top + height
return (bottom > viewport_top) && (top < viewport_bottom)
}
// Animation when element is in view
window.addEventListener('scroll', function (e) {
if (isOnScreen('#lfdesign')) {
$("#lfdesign .flexcontainer").addClass("animated slower bounceInRight");
}
if (isOnScreen('#books')) {
$("#books .flexcontainer").addClass("animated slower bounceInLeft");
}
if (isOnScreen('.srp-color')) {
$(".srp-color .flexcontainer").addClass("animated slower bounceInRight");
}
if (isOnScreen('.nescol-color')) {
$(".nescol-color .flexcontainer").addClass("animated slower bounceInLeft");
}
if (isOnScreen('.worldskills-color')) {
$(".worldskills-color .flexcontainer").addClass("animated slower bounceInRight");
}
if (isOnScreen('.silvernote-color')) {
$(".silvernote-color .flexcontainer").addClass("animated slower bounceInLeft");
}
});
CSS
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css">
HTML
<section class="left silvernote-color">
<div class="flexcontainer">
<div class="flex-item">
<a href="webdesign/silvernote">
<img class="normal" src="img/silvernote-logo.png" alt="The logo for SilverNote, a music enterprise hub in Aberdeen who I helped to create a responsive site for by adding artist profiles with a team of developers">
</a>
</div>
<div class="flex-item">
<p>SilverNote Music is a music enterprise hub in Aberdeen that want to promote new music. They tasked me and three others to work as a team to create a responsive site that catered to those viewing on computers as well as a younger, mobile audience.</p>
<p>My main responsibilities were communicating with our client and developing the artist page. I pitched the prototype to the wider organisation and gathered feedback from the band members. This feedback informed the music players I created on the artist profiles.</p>
</div>
</div>
</section>
The reason why you're seeing TypeError: $elem.offset(...) is undefined is because you're trying to call the offset method on an element that does not exist on your page.
You should change your logic that checks if an element exists. Currently, your function isOnScreen takes a string as an argument and checks if the length of that string is 0 - this doesn't actually check if the element is on the page or not.
function isOnScreen(elem) {
if (elem.length == 0) {
return
}
}
window.addEventListener(`scroll`, function (e) {
if (isOnScreen(`#lfdesign`) {
// passing a string and checking it's length
// inside the `isOnScreen` function doesn't tell
// you if this element is on the page or not
}
})
Instead, what you should do is try to grab/find the element first with jQuery, if that's what you prefer, and check if exists. If so, do whatever you need to do, otherwise return early.
function isOnScreen(selector) {
var el = $(selector)
if (el.length !== 1) {
console.log(`element ${selector} does not exist!`)
return;
}
console.log(`element ${selector} exists!`)
// do work
}
// inside of `window.addEventListener` callback function
if (isOnScreen("#lfdesign")) {
// do work
}
if (isOnScreen("#books")) {
// do work
}
if (isOnScreen(".srp-color")) {
// do work
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="lfdesign">
<h4>My ID is <b>lfdesign</b>!</h4>
</div>
<div id="books">
<h4>My ID is <b>books</b>!</h4>
</div>
One note that I'd like to make is that window.addEventListener("scroll", callback) will get called a lot in a short amount of time which might cause performance issues. If you're running into issues with performance, you should look at throttling and/or debouncing:
The Difference Between Throttling and Debouncing - CSS Tricks
Hope this helps.
Fixed using jQuery.inview plugin instead.

I can't get the animation speed to work

I'm trying to make a menu (with buttons) that open links.
when you hover on the buttons, a slideDown reveals more information on that link.
I've gotten all those features to technically work, however i can't get the animation speed to go any slower than instantly.
I'm really new to javascript and Jquery, and it took me 2-3 days to get the javascript and CSS to do what i have so far... and yeah it's probably bloated... but i'm still proud i got this done so far :D
PS, I know most menus are made w/ul's but I really like the way the buttons look and detested trying to put the list together. last time i tried used a seperate ul for the information and it kept styling the second list like the first because it was inside it... so annoying. I also tried vertical-link list w/CSS but still think flat 'buttons' are so boring. i really like the 3D esk of the actual html
HTML:
<div class="mainmenu">
<div id="homemenu">
<button id="home" class="mmbutton active">Home</button>
<div id="homesub" class="sub active">-just a bit about this page</div>
</div>
<div id="photosmenu">
<button id="photos" class="mmbutton">Photos</button>
<div id="photossub" class="sub inactive">-just a bit about this page
</div>
</div>
</div>
javascript/jquery:
$(function(){
var mmbutton = $('.mmbutton');
var start = "http://";
var address = "[my web address"; //add "http:
var about = "[web address]/aboutme.html";
var id = 0;
var rel = 0;
var mmsub = 0;
//<click link buttons:
$(mmbutton).click(function(){
var id = $(this).attr('id');
if (id === "home") {
location.replace(start+address);
}else if (id === "about") {
window.alert("I'm sorry I don't have this page set up yet. Thank you for visiting my page!");
//add additional buttons here under 'else if' unless its a subdomain
}else {
location.replace(start+id+'.'+address);//goes to any subdomain by id
}});
//>detect hover
$(mmbutton).hover(function(){
id = $(this).attr('id');
rel = '#'+id+'sub';
mmsub = '#'+id+'menu';
console.log('mouseenter'+rel);
$(rel).removeClass('inactive');
$(rel).stop().slideDown(500000);
}, function(){
console.log('mouseleave'+rel);
$(rel).addClass('inactive');
if ( $(this).hasClass('active')) {
$(rel).removeClass('inactive');
console.log('this is active');
}if ($(rel).hasClass('inactive')){
$(rel).stop().slideUp(500000);
}});});
relevante CSS:
.inactive {
display: none;
}
.sub {
transition-duration: 1s;
}
You can do it setting all that info divs to display:none and use slideToggle() function for that. Considering you want to keep the subdiv's opened when you're over them, one option is create a span element that include the button and the subdiv, and apply the hover to that span. So...
HTML:
<div class="mainmenu">
<div id="homemenu">
<span class="subcontainer">
<button id="home" class="mmbutton active">Home</button>
<div id="homesub" class="sub">-just a bit about this page</div>
</span>
</div>
<div id="photosmenu">
<span class="subcontainer">
<button id="photos" class="mmbutton">Photos</button>
<div id="photossub" class="sub">-just a bit about this page</div>
</span>
</div>
</div>
CSS:
.sub {
display: none;
/*transition-duration: 1s; IMPORTANT: REMOVE THIS!!*/
}
JQUERY:
$('span.subcontainer').hover(function() {
$(this).find('div.sub').slideToggle('slow');
});
IMPORTANT: Check that to make it work you have to remove the transition style you've created for .sub divs (it interfeers with the jquery function).
NOTE: I don't use the div.homemenu or div.photosmenu as the containers for the hover because div's normally have some styles pre-applied by default and can interfeer with the desired behaviour (for example, they normally have width=100% so the hover applies even when you're outside of the button or subdiv in the same line). span is normally more innocuous to use it just as a wrapper.
I hope it helps
Oh! i got it. i was trying to do too much (show off.../ using what im learning).
I removed the line that added and removed the class 'inactive' and just toggled the SlideUp and slideDown when i wanted it too. now i can adjust the animation speed:
(HTML remains unchanged)
CSS: removed the "transition-duration: 1s;"
JavaScript:
$(function(){
var mmbutton = $('.mmbutton');//any/all buttons
var activebut= 0; //detect button classes
var mmdiv = $("div[id$='menu']");//detect button sub info
var start = "http://";
var address = "[address]/index.html"; //add "http://" + [blog or games] + address
var about = "http://[address]/aboutme.html";
var id = 0;
var sub = 0;
var slidespeed= 450; //slideUP/slideDown animation speed //added var for speed
//<click link buttons: (unchanged)
$(mmbutton).click(function(){
id = $(this).attr('id');
if (id === "home") {
location.replace(start+address);
}else if (id === "about") {
location.replace(start+'[address]/aboutme/index.html')
//add additional buttons here under 'else if' unless its a subdomain
}else {
location.replace(start+id+'.'+address);//goes to any subdomain by id
}
});
//<hover display:
//<detect mouse ON button
$(mmbutton).hover(function(){
id = $(this).attr('id');
sub = '#'+id+'sub';
activebut= $(this);
if ( $(activebut).hasClass('active')) {
}else {
$(sub).stop().slideDown(slidespeed);
}
});
//<detect mouse off button AND div
$(mmdiv).mouseleave(function(){
if ( $(activebut).hasClass('active')) {
}else {
$(sub).stop().slideUp(slidespeed);
}
});
});

Updating text on a DIV causes it to update on the previous DIV's already clicked - jquery

I've been trying to solve this for the last couple of hours but all attempts failed...
What I need to do is to click on a DIV that will cause another DIV to pop up.
Inside the pop up there are some <li> and after clicking it, the attribute name is "transferred" to the first DIV clicked.
Ok, I managed to do that but after I update the first DIV and when I try to update the second DIV, the first DIV also gets updated and when I try to update the third DIV the other two gets updated ass well.
Can anyone help me to fix it and only update the DIV it was clicked on leaving the previous as it was supposed to be?
Here is the code:
HTML
<div class="num-1">
<img src="http://dummyimage.com/180x180/eeeeee/000000.jpg" width="180" height="180">
<p class="brand"></p>
<p class="name"></p>
</div>
<div class="num-2">
<img src="http://dummyimage.com/180x180/eeeeee/000000.jpg" width="180" height="180">
<p class="brand"></p>
<p class="name"></p>
</div>
<div class="num-3">
<img src="http://dummyimage.com/180x180/eeeeee/000000.jpg" width="180" height="180">
<p class="brand"></p>
<p class="name"></p>
</div>
<div class="num-4">
<img src="http://dummyimage.com/180x180/eeeeee/000000.jpg" width="180" height="180">
<p class="brand"></p>
<p class="name"></p>
</div>
<div class="num-5">
<img src="http://dummyimage.com/180x180/eeeeee/000000.jpg" width="180" height="180">
<p class="brand"></p>
<p class="name"></p>
</div>
<div class="popup">
<ul>
<li name="{{PHP GENERATED $name}}">{{PHP GENERATED $name}}</li>
<li name="{{PHP GENERATED $name}}">{{PHP GENERATED $name}}</li>
<li name="{{PHP GENERATED $name}}">{{PHP GENERATED $name}}</li>
</ul>
</div>
jQuery
//Openning the .popup and assigning the names
//First check the number of the div it was clicked on
$('img').on('click', function() {
var num = $(this).parent().attr('class').match(/\d+/)[0];
$('.popup').fadeIn();
//Click li to update the brand and name on the page
$('.popup li').on('click', function() {
//Check the name and split it
var nameComplete = $(this).attr('name');
var Array = nameComplete.split(" ");
//Check the first word and identify it as brand, also update on the page
var brand = Array[0];
$('.num-' + num + ' .brand').text(brand);
//Check the rest of the array for the name and update it on the page
var name = '';
for(var i=1; i<Array.length ;i++) {
name = name + Array[i] + ' ';
}
name = $.trim(name);
$('.num-' + num + ' .name').text(name);
$('.popup').fadeOut();
});
});
Someone might wonder if I'm getting the number of the DIV correctly.
I did console.log(num); and it shows that I'm clicking on the correct DIV because the DIV class is num-X (X = 1 to 5), 1 for 1, 2 for 2,...
The names of the <li> are ok, it works as it should, split it and get the right part to "transfer".
I guess I made the proper modifications to better understanding the code.
$('img').on('click', function() {
var num = $(this).parent().attr('class').match(/\d+/)[0];
window.num = num;
$('.popup').fadeIn();
});
//Click li to update the brand and name on the page
$('.popup li').on('click', function() {
//Check the name and split it
var nameComplete = $(this).attr('name');
var Array = nameComplete.split(" ");
//Check the first word and identify it as brand, also update on the page
var brand = Array[0];
$('.num-' + window.num + ' .brand').text(brand);
//Check the rest of the array for the name and update it on the page
var name = '';
for(var i=1; i<Array.length ;i++) {
name = name + Array[i] + ' ';
}
name = $.trim(name);
$('.num-' + num + ' .name').text(name);
$('.popup').fadeOut();
});
Separate both the click events in separate functions. Probably whats happening in your code is that every time you click on a div a new click event handler is attached to the '.popup li' and every time you click on one of the '.popup li' all the click handlers are activated. Thus explaining the multiple divs being updated.
Second option you have is to use jquery.off. This way every time you attach an event handle with .click to the '.popup li' you also remove it after you work is done and complete. Since this would have made your code harder to debug and to read i have preferred the first method in this answer.
NOTE**- i have changed the num to window.num which isnt a very good practice (keeping a global variable) but im sure you will be able to refactor the code once you get it to work

Creating a simple "Memory" card game. Need to allow only two clicks at a time

I am creating a simple Memory card game. I have the structure and card designs set up with CSS, now I just need to enable the actual playing of the game.
I need to have only two clicks allowed at a time. If the two cards match, the cards are removed. If the two cards don't match, they are "flipped" back over.
...I am only including the first row of the game, even though it is a 4x4 setup.
HTML
<div class="cardHolder">
<div id="card1" class="card" onClick="revealBlueCard()"></div>
<div id="card2" class="card" onClick="revealGreenCard()"></div>
<div id="card3" class="card" onClick="revealGreenCard()"></div>
<div id="card4" class="card" onClick="revealBlueCard()"></div>
</div>
JAVASCRIPT
// blue cards
var card1Ref = document.getElementById("card1");
var card4Ref = document.getElementById("card4");
card1Ref.addEventListener("click", revealBlueCard);
card4Ref.addEventListener("click", revealBlueCard);
function revealBlueCard(event){
event.target.style.backgroundColor = "#2155a8";
event.target.innerHTML = "<p>8</p>";
}
// green cards
var card2Ref = document.getElementById("card2");
var card3Ref = document.getElementById("card3");
card2Ref.addEventListener("click", revealGreenCard);
card3Ref.addEventListener("click", revealGreenCard);
function revealGreenCard(event){
event.target.style.backgroundColor = "#3cb260";
event.target.innerHTML = "<p>3</p>";
}
I am still very new to Javascript. I am assuming I am using a click event for "card" and applying if statements. I am also wondering if I need to apply data attributes to each card.
Why don't you add classes to the cards like blue class and green class.
CSS:
.blue{
background: #2155a8;
}
.green{
background: #3cb260
}
and also use event delegation (try to avoid adding event listeners to each card)
document.querySelector('#cardHolder').addEventListener('click', function(evt){
var clickedCard = evt.target;
if(clickedCard.classList.contains('blue'){
//do whatever for blue card
} else if(clickedCard.classList.contains('green')){
//do whatever for green card
}
});
there's a better way:
Use
onclick="cardClicked(this)"
for all your cards.
then in js
function cardClicked(elCard) {
if (elCard.classList.contains('flipped')) {
return;
}
// Flip it
elCard.classList.add('flipped');

jquery replace issues

I have series of divs, that represent similar to a chess board. but this is a kinda tricky chess. basically users clicks on a piece, and clicks on enemy piece. if it can kill. it should move it to the enemies position. And enemy should be deleted.
<div class="column" data-square="4-4">
<div class="white-king lol">a</div>
</div>
<div class="column" data-square="4-5">
<div class="black-pawn lol">b</div>
</div>
<div class="column" data-square="4-6">
<div class="blue lol">c</div>
</div>
//so when users first clicks on whiteking, we get the data-square, assign it to a variable $from, then clicks on enemy black-pawn. does some validation in server, and should move the
​
inner div of square = 4-4 to inner div of square = 4-5 , the inner div of square = 4-5 should be deleted and the inner div of square= 4-4 should be presnet
I have tried using jquery clone. but it doesnt works out well
fiddle: http://jsfiddle.net/jm4eb/13/
I personally wouldn't get too bent out of shape replacing one element with another. I'd look at just swapping the attributes from one element to another. Remove the white-king class from one element and add it to another.
This is not the good way still, but you can try
(function() {
var from = null;
var $change =null;
var to = null;
$(".column" ).click(function(){
if(from === null)
{
$(this).css("background-color","yellow");
from = $(this).data('square');
$change= ($('<div/>').append($(this).clone(true).children()).html());
}
else
{
to = $(this).data('square');
$(this).html("").html($change);
$('div[data-square="'+from+'"]').html("<div class=lol>empty</div>").css("background-color","");
from = to = null;
}
});
}());​

Categories