Making the drag and drop work in modified version

I have a codepen which was created by @PaulOB’s in response to my question back in 2023. Here’s the exact post from where I got the codepen.

In the above codepen, when we drag a blue rectangle box on the squares, it is possible to move the dropped blue rectangle content within the squares. For example, if I want to drop the blue rectangle containing Late for The Sky on the square A1, it will look like this:

And I can hold the content of A1 and drag and drop it to A2 or any other cell as per my wish. And I was looking at the code Js code in the codepen that does this.

Modified Version:

I have a modified version, where I’m putting things on the square, using a button click and not dragging a blue rectangular text box as shown in this JSFiddle. Steps are shown below:

  1. We select an option from the Select Options dropdown menu. Option One has already been selected for our convenience.
  2. We select a color for the background. It doesn’t work in the JSFiddle but it does work in my actual code so we can ignore that for time being.
  3. We click Show Options button to list the options associated with Select options dropdown.
  4. We enter a row A-H, for example, A
  5. We enter a column number from 1-12 , for example 1
  6. We hit the calculate button and then click on move text content button which moves the content on the squares.

Now after the contents are on the squares, if I try to move any cell content to an empty cell, it won’t move . I still have the UI draggable code in my JsFiddle code starting from line 40 (revert function is not needed as I’m using button based approach like in the code pen shown above but I still have kept it as it is)

What things I may need to keep in mind in order to move the content of cells on any other empty cell?Or why it is not working with the modified version?

Oh no not this again :slight_smile:

I guess its because the content you are trying to move doesn’t exist when the draggable code is first run. Maybe you could call the draggable after you create the content. e.g. Put it in a function and then call it when you have created the items.

If that doesn’t work then wait for one of the JS gurus to pop in as there may be better solutions. :slight_smile:

Didn’t we already create something with buttons and some very convoluted reasoning for placing the content in half columns? I seem to have a load of these half demos that I now have no idea of what they were doing :slight_smile:

1 Like

Maybe like this:

I added the call next to where you have this comment:


...etc
    // $("#phrase").append(html);
     getPhrase()  /* I added this here to call draggable as the items now exist */
...etc

/* then I wrapped a function around this existing code */
function getPhrase() {
  $(".words").draggable({
    revert: function (event, ui) {
      var bRevertingPhrase = this.closest("#drop-em")

      if (!bRevertingPhrase.length) {
        var phraseID = $(this).data("id")
        var phraseHomeID = $(this).parent().data("id")

        //If the child and parent ids don't match, we move the child to the correct parent
        if (phraseID !== phraseHomeID) {
          var correctCell = $("#phrase").find("div[data-id='" + phraseID + "']")
          correctCell.prepend(this)
        }
      }
      return !event
    },
  })
  $("#drop-em > div").droppable({
    drop: function (ev, ui) {
      $(ui.draggable)
        .detach()
        .css({ top: 0, left: 0 })
        .appendTo($(this).find(".content:empty"))
      //$("#move-text").addClass("disabled");
    },
  })
  $("#phrase > div").droppable({
    drop: function (ev, ui) {
      $(ui.draggable).detach().css({ top: 0, left: 0 }).prependTo(this)
    },
  })
} /* close new function*/

Thanks. This worked. Here’s the modified JS Fiddle. I have added getPhrase() function inside if and else both to make it work as there are two dropdowns.

Sorry for bringing this complicated code again. Appreciate your quick response on it and looking into it again.

Yeah, this JSFiddle is the one I think I last settled on as I am using pretty similar thing in my actual code. I am unable to find out exact old post of yours related to the same.

Next thing I need to figure out is how to take the background color with the text as well . I have the blue color background color working in above JSFiddle and wanted to see if you have any thoughts about it?

You probably would have been better using classes rather than writing color=#ff000 etc into the tag (which is invalid anyway as # us reserved for ids).

If you write the color into the span then you can use the :has selector in css to do the colouring along with using :empty to rub the original out when its moved.

This is just proof of concept and could be better done with classes I think.

the only change to the JS was adding this line in the middle here:

    newLoc.classList.add("color-" + selectedColor);
    cell.classList.add("col-" + selectedColor); /* added this only*/
    console.log("Test with codes");

The rest was done with the css at the bottom of the css panel.

Thanks. I will consider looking into class based approach.

A couple of other things I was wondering about were the following, and wanted to discuss your thoughts on this:

  1. If I have to retrieve what cell number it is ( for example, A1 calculates 43) after the item has been dragged over to another square, the following thought process came to my mind:

When the Move Text Content! button is clicked in our demo, the cell value is calculated at the beginning. Although, the demo examples in codepen and JSfiddle we are using is putting data-id="0" in the span tag of an item which is on square A1. For example, the span tag looks like this:

But in my actual code, I’m putting the calculated cell number value for data-id. So if I have something sitting on square A1, my span tag will contain data-id="43"

Yet to make draggable changes in my actual code hence no classes are shown in above span tag.

But it’s like a black box and impossible to determine in advance where the user is going to drag the content of one square to another. So I suppose I should hard code all 96 divs with the actual value of their cell numbers and use that once item is moved from one square to a new one - right?

  1. Secondly, I will be doing some Ajax operations in my JS once the item is on the new square as I would want to update some details in my database once it is moved on to a new square. I guess, the following drop function looks like a correct place to do the updates using Ajax call ?
 $("#phrase > div").droppable({
    drop: function (ev, ui) {
      $(ui.draggable).detach().css({ top: 0, left: 0 }).prependTo(this);
    }
  });

No the id on the span relates to the original option (when you click show options) and not the square it is placed on. That was from the first demo where you dragged the options onto the squares but could also drag them back to the options but only drop them in the correct place. In my codepen above you can still drag the text back to the option list and have it it placed only on the correct option.

I am unsure of your purpose here. What is it that you need to know and why? If perhaps you are saving the new positions then I guess you could just run through the list of squares and grab the square values of the ones that hold content. If its easier you could just assign a reference or id to the square but you’d still have to cycle through the whole list to find the ones with content in them and then you would have a reference to the square anyway.

That’s a question for one of the JS gurus here. :slight_smile: (Maybe @rpg_digital or anyone else can help if around?)

A quick web search tells me that something like this should work.

$("#phrase > div").droppable({
  drop: function (ev, ui) {
    // Move the draggable to the drop target
    $(ui.draggable).detach().css({ top: 0, left: 0 }).prependTo(this);

    // Perform AJAX call
    $.ajax({
      url: '/your-endpoint', // Replace with your actual endpoint
      method: 'POST',
      data: {
        itemId: $(ui.draggable).data('id'), // example data
        targetId: $(this).attr('id') // example data
      },
      success: function (response) {
        console.log('Drop successful:', response);
      },
      error: function (xhr, status, error) {
        console.error('Drop failed:', error);
      }
    });
  }
});

I will be updating the new square position once content has been moved from old square to new one and hence I would need to know the same.

Is it possible to disable the drag option from blue rectangles to squares? I don’t want this (image below). Similarly, dragging the text back to the option list won’t be required anymore. Maybe moving getPhrase() function inside the code when Move Text Content is clicked might be a good idea?

I was wondering if I can have the drag and drop options to work once these options lists are moved to the squares using Move Text Content! button. Basically drag and drop should work only within the 96 squares.

Just FYI - This thing works on another codepen I created and on a WAMP server setup where I just have one index.html with all CSS, HTML and JS in one page. For some reason it didn’t work in JSFiddle ( I mean the color thing, drag and drop works fine). I also tried it in my actual code but only drag and drop was working and not the color thing. I will need to investigate it in my actual code as I have different JS files being included in my main page and that might be causing some issues.

This thing is working fine on my actual code as well. I was missing the CSS that you added to handle to escape classname as # is reserved for ids

Then i think you will need to move the getPhrase call inside the button click instead and remove the checking of the ids.

e.g.

I assume you won’t want it dragged on top of something else so maybe this is better.

1 Like

I have a question on a slight modification of your latest codepen. Here’s my modified CodePen:

Modification I have done:
Prerequisite: Click the Calculate button first before doing anything so that we have cell value available.

When the Move Text Content button is clicked, I’m adding the cell values for data-id in the HTML as shown below:

Code Changes I did:

  1. Added a function on line 181
//Added a function
function getCellValue (){
  console.log("Returning starting cell value which is as follows:");
  let startingCellValue = $("#cellnum").html();
  console.log(startingCellValue);
  return startingCellValue;
}

And modified the showOptions() function to handle the HTML on line 16 as shown in the code pen above.

This is close to what I have done in my actual code as I am saving the cell values in my database when Move Text Content! button is clicked. Now the problem I’m running into is how can I determine the new cell value once an item is dragged over from , let’s say square A1 which has data-id = 43 in the above setup to square B1. B1’s data value should be 37. I am wondering if there is a way to know what cell value it is going to be just before square A1 reaches it’s new square position (in our example B1). If not, then maybe I need to somehow update the data-id = 37 when square A1 content is dragged over to B1 right?

P.S.

For some reason I’m not able to paste HTML content here as I was planning to do by pasting the showOptions function and I keep getting draft offline while replying to this message. Similarly, I had to use image to display HTML inpected content above.

If you number all the .item divs with the cell reference from the start then when you then drop an item you can grab that data-id from the parent quite easily.

Here’s an updated codepen.

When you drag an item to a new square you will see that the span data-id is changed to match the pre existing data-id on the .item div.

I’m sure it could be simplified but I didn’t want to try and amalgamate the routines so I left the initial cell numbering separate.

1 Like

Seems to be a bug since a recent update and its being looked at. Its a bit of a pain :frowning:

1 Like

Thanks. I am curious to know how the following line is able to get the new square id? We haven’t even called the getCellValue() to get the cell value here.


// Get the new parent container's data-id
        const parentDataId = $(this).data("id");

Its already there. I wrote all the ids into each .item div right at the start. It never needs to be called again.

When you drag an item and drop it it just takes the id from that parent where its been dropped.

Look at the html in devtools and you will see every square is labelled with the correct id.

1 Like

Thanks. I was missing this piece of code in my actual code. Makes sense now.

const allCells = document.querySelectorAll(".item"); 

for (let i = 0; i < allCells.length; i++) {
  const row = Math.floor(i / 12); // Row index (0 to 7)
  const col = i % 12; // Column index (0 to 11)

  let dataId;
  if (col < 6) {
    dataId = 43 - row * 6 + col;
  } else {
    dataId = 91 - row * 6 + (col - 6);
  }

  allCells[i].setAttribute("data-id", dataId);
}

I’m coming back to this to avoid running into the issue of dealing with reserved # for IDs. Were you suggesting I have blue, red, green, etc., classes in (maybe) a separate CSS file?

Right now I have these colors saved in a database table like the following, which I’m not making much use in my actual code except making an Ajax call and populating the Colors dropdown menu in the UI. I’m even thinking of getting rid of it completely as I don’t have need to perform database related operations (update, delete, add etc) to this table.

You have those already except that you added an invalid hash in the middle of the class. I would just remove the hash and let the css style the background.

e.g.
Remove the hash from here like this:

 <select id="lineupColors">
      <!--   <option value="-">select</option> -->
      <option selected="selected" value="0000ff">Blue</option>
      <option value="ffa500">Orange</option>
      <option value="ff0000">Red</option>
      <option value="3cb371">Green</option>
      <option value="ee82ee">Purple</option>
      <option value="964B00">Brown</option>
      <option value="000000">Black</option>
      <option value="FF00FF">Magenta</option>
    </select>

Then you can remove the escape from the CSS:

.content:has(.col-0000ff) {
  background: #0000ff;
}
.content:has(.col-ffa500) {
  background: #ffa500;
}
.content:has(.col-ff0000) {
  background: #ff0000;
}
.content:has(.col-3cb371) {
  background: #3cb371;
}
.content:has(.col-ee82ee) {
  background: #ee82ee;
}
.content:has(.col-964B00) {
  background: #964b00;
}
.content:has(.col-000000) {
  background: #000000;
}
.content:has(.col-ff00ff) {
  background: #ff00ff;
}

Then you can remove the js that is adding the background color.

/*
    $(".color-" + CSS.escape(selectedColor)).css(
      "background-color",
      selectedColor
    );*/

Which leaves you with this:

1 Like

Thanks Paul. I’m looking into one CSS issue now.

Basically, wherever we have numbers inside the squares with red circles, I’m trying to have something similar on the top right corner to start implementing a delete functionality. And for this, I’m first trying to mess with the existing CSS content property and adding a trash image as follows:

Used content:url("https://i.sstatic.net/IVNy9nWk.png");

.item .content::before {
/*   content: counter(cellCount); */
  content:url("https://i.sstatic.net/IVNy9nWk.png");
  position: absolute;
  top: 2px;
  left: 2px;
  font-size: smaller;
  color: #666;
  border-radius: 50%;
  border: 1px solid red;
  background: white;
  width: 1.2rem;
  height: 1, 2rem;
  display: grid;
  place-items: center;
}

But it doesn’t works as shown in the codepen below:

On my local setup using XAMP server, I’ve also tried downloading the image and then using the content property like this:

content: image-set("trash.png",2x);

That didn’t work as well.

Hence, I’m wondering if I’m heading in the right direction or not? Can the content displayed using CC content property be used todo what I’m looking for?

Eventially my end goal is to have this image clickable such that I can call an Ajax for that particular square to delete that functionality.

I would just add it as a background image and then size to suit.

e.g.

.item .content::before {
/*   content: counter(cellCount); */
  content:"";
  background:url(https://i.sstatic.net/IVNy9nWk.png);
  background-size:contain;
  position: absolute;
  top: 2px;
  left: 2px;
  font-size: smaller;
  color: #666;
  border-radius: 50%;
  width: 1.2rem;
  height: 1.2rem;
  display: grid;
  place-items: center;
}

However codepen doesn’t like that link (https://i.sstatic.net/IVNy9nWk.png)) so either download the image into your codepen assets or use another one.

Unfortunately pseudo elements are not clickable with js as they are not in the dom as such. You would have to use an element in the html instead or use a workaround such as this example I found.