Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Zedcars

macrumors 6502
Original poster
Apr 5, 2010
406
718
Brighton, UK
Hello,

I'm trying to code the creation of 'sticky notes' on a webpage with a contextual menu right-click option. I nearly have it working, but there are a couple of things I cannot seem to figure out (I've been trying for a couple of weeks now!).

I'm very new to web development (beginner level).

I've combined some code for the sticky notes and context right-click menu. The problem I'm having is that when the page loads for the first time, a sticky note appears: I do not want that to happen. I would like it to initially load with no sticky notes visable, and only make them visable once they are added, or if they were previously added and automatically saved (which is already part of the code).

So I used `display: none` to hide the css "note" element. However, if I do that then when I right-click to add a note it doesn't show up at all. So I understand I need to tell it to show if it's been created by my right-click menu option, but I've been struggling to understand how to do this.

The second issue I have is that the sticky notes are created in random positions; whereas, I would like them to show up next to where I have right-clicked.

Please could someone point me in the right direction. I apologise for my lack of basic knowledge.

Here is what I have so far:

HTML

HTML:
<!DOCTYPE html>
<html>
<head>
   <title>Custom Right Click Menu</title>
   <link rel="stylesheet" type="text/css" href="stickies.css" />

   <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
   <script type="text/javascript" src="stickies.js"></script>

</head>
<body>

<!-- Menu div initially hidden -->
<script>
document.getElementById("newNoteButton").disabled = !db;
</script>
<div class="menu" id="newNoteButton" onclick="newNote()">
   <ul>
       <a href="#"><li>Add Note</li></a>
   </ul>
</div>
</body>
</html>

CSS

Code:
.menu{
    width: 100px;
    background: #000;
    color: #fff;
    position:absolute;
    z-index: 999999;
    display: none;
    box-shadow: 0 0 10px #713C3C;
}
.menu ul{
    list-style: none;
    padding: 0;
    margin:0;
}
.menu ul a{
    text-decoration: none;
}
.menu ul li{
    width: 88%;
    padding: 6%;
    background-color: #F04D44;
    color: #fff;
}
.menu ul li:hover{
    background-color: #F7BA4B;
    color: #444343;
}

body {
    font-family: 'Lucida Grande', 'Helvetica', sans-serif;
}

.note {
    background-color: rgb(255, 240, 70);
    height: 100px;
    padding: 10px;
    position: absolute;
    width: 200px;
    -webkit-box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5);
}

.note:hover .closebutton {
    display: block;
}

.closebutton {
    display: none;
    background-image: url(deleteButton.png);
    height: 30px;
    position: absolute;
    left: -15px;
    top: -15px;
    width: 30px;
}

.closebutton:active {
    background-image: url(deleteButtonPressed.png);
}

.edit {
    outline: none;
}

.timestamp {
    position: absolute;
    left: 0px;
    right: 0px;
    bottom: 0px;
    font-size: 9px;
    background-color: #db0;
    color: white;
    border-top: 1px solid #a80;
    padding: 2px 4px;
    text-align: right;
}

Javascript

Code:
$(document).ready(function () {
        $("html").on("contextmenu",function(e){
               //prevent default context menu for right click
               e.preventDefault();

               var menu = $(".menu");

               //hide menu if already shown
               menu.hide();

               //get x and y values of the click event
               var pageX = e.pageX;
               var pageY = e.pageY;

               //position menu div near mouse cliked area
               menu.css({top: pageY , left: pageX});

               var mwidth = menu.width();
               var mheight = menu.height();
               var screenWidth = $(window).width();
               var screenHeight = $(window).height();

               //if window is scrolled
               var scrTop = $(window).scrollTop();

               //if the menu is close to right edge of the window
               if(pageX+mwidth > screenWidth){
                menu.css({left:pageX-mwidth});
               }

               //if the menu is close to bottom edge of the window
               if(pageY+mheight > screenHeight+scrTop){
                menu.css({top:pageY-mheight});
               }

               //finally show the menu
               menu.show();
        });

        $("html").on("click", function(){
            $(".menu").hide();
        });
    });


var db = null;

try {
    if (window.openDatabase) {
        db = openDatabase("NoteTest", "1.0", "HTML5 Database API example", 200000);
        if (!db)
            alert("Failed to open the database on disk.  This is probably because the version was bad or there is not enough space left in this domain's quota");
    } else
        alert("Couldn't open the database.  Please try with a WebKit nightly with this feature enabled");
} catch(err) {
    db = null;
    alert("Couldn't open the database.  Please try with a WebKit nightly with this feature enabled");
}

var captured = null;
var highestZ = 0;
var highestId = 0;

function Note()
{
    var self = this;

    var note = document.createElement('div');
    note.className = 'note';
    note.addEventListener('mousedown', function(e) { return self.onMouseDown(e) }, false);
    note.addEventListener('click', function() { return self.onNoteClick() }, false);
    this.note = note;

    var close = document.createElement('div');
    close.className = 'closebutton';
    close.addEventListener('click', function(event) { return self.close(event) }, false);
    note.appendChild(close);

    var edit = document.createElement('div');
    edit.className = 'edit';
    edit.setAttribute('contenteditable', true);
    edit.addEventListener('keyup', function() { return self.onKeyUp() }, false);
    note.appendChild(edit);
    this.editField = edit;

    var ts = document.createElement('div');
    ts.className = 'timestamp';
    ts.addEventListener('mousedown', function(e) { return self.onMouseDown(e) }, false);
    note.appendChild(ts);
    this.lastModified = ts;

    document.body.appendChild(note);
    return this;
}

Note.prototype = {
    get id()
    {
        if (!("_id" in this))
            this._id = 0;
        return this._id;
    },

    set id(x)
    {
        this._id = x;
    },

    get text()
    {
        return this.editField.innerHTML;
    },

    set text(x)
    {
        this.editField.innerHTML = x;
    },

    get timestamp()
    {
        if (!("_timestamp" in this))
            this._timestamp = 0;
        return this._timestamp;
    },

    set timestamp(x)
    {
        if (this._timestamp == x)
            return;

        this._timestamp = x;
        var date = new Date();
        date.setTime(parseFloat(x));
        this.lastModified.textContent = modifiedString(date);
    },

    get left()
    {
        return this.note.style.left;
    },

    set left(x)
    {
        this.note.style.left = x;
    },

    get top()
    {
        return this.note.style.top;
    },

    set top(x)
    {
        this.note.style.top = x;
    },

    get zIndex()
    {
        return this.note.style.zIndex;
    },

    set zIndex(x)
    {
        this.note.style.zIndex = x;
    },

    close: function(event)
    {
        this.cancelPendingSave();

        var note = this;
        db.transaction(function(tx)
        {
            tx.executeSql("DELETE FROM WebKitStickyNotes WHERE id = ?", [note.id]);
        });

        var duration = event.shiftKey ? 2 : .25;
        this.note.style.webkitTransition = '-webkit-transform ' + duration + 's ease-in, opacity ' + duration + 's ease-in';
        this.note.offsetTop; // Force style recalc
        this.note.style.webkitTransformOrigin = "0 0";
        this.note.style.webkitTransform = 'skew(30deg, 0deg) scale(0)';
        this.note.style.opacity = '0';

        var self = this;
        setTimeout(function() { document.body.removeChild(self.note) }, duration * 1000);
    },

    saveSoon: function()
    {
        this.cancelPendingSave();
        var self = this;
        this._saveTimer = setTimeout(function() { self.save() }, 200);
    },

    cancelPendingSave: function()
    {
        if (!("_saveTimer" in this))
            return;
        clearTimeout(this._saveTimer);
        delete this._saveTimer;
    },

    save: function()
    {
        this.cancelPendingSave();

        if ("dirty" in this) {
            this.timestamp = new Date().getTime();
            delete this.dirty;
        }

        var note = this;
        db.transaction(function (tx)
        {
            tx.executeSql("UPDATE WebKitStickyNotes SET note = ?, timestamp = ?, left = ?, top = ?, zindex = ? WHERE id = ?", [note.text, note.timestamp, note.left, note.top, note.zIndex, note.id]);
        });
    },

    saveAsNew: function()
    {
        this.timestamp = new Date().getTime();

        var note = this;
        db.transaction(function (tx)
        {
            tx.executeSql("INSERT INTO WebKitStickyNotes (id, note, timestamp, left, top, zindex) VALUES (?, ?, ?, ?, ?, ?)", [note.id, note.text, note.timestamp, note.left, note.top, note.zIndex]);
        });
    },

    onMouseDown: function(e)
    {
        captured = this;
        this.startX = e.clientX - this.note.offsetLeft;
        this.startY = e.clientY - this.note.offsetTop;
        this.zIndex = ++highestZ;

        var self = this;
        if (!("mouseMoveHandler" in this)) {
            this.mouseMoveHandler = function(e) { return self.onMouseMove(e) }
            this.mouseUpHandler = function(e) { return self.onMouseUp(e) }
        }

        document.addEventListener('mousemove', this.mouseMoveHandler, true);
        document.addEventListener('mouseup', this.mouseUpHandler, true);

        return false;
    },

    onMouseMove: function(e)
    {
        if (this != captured)
            return true;

        this.left = e.clientX - this.startX + 'px';
        this.top = e.clientY - this.startY + 'px';
        return false;
    },

    onMouseUp: function(e)
    {
        document.removeEventListener('mousemove', this.mouseMoveHandler, true);
        document.removeEventListener('mouseup', this.mouseUpHandler, true);

        this.save();
        return false;
    },

    onNoteClick: function(e)
    {
        this.editField.focus();
        getSelection().collapseToEnd();
    },

    onKeyUp: function()
    {
        this.dirty = true;
        this.saveSoon();
    },
}

function loaded()
{
    db.transaction(function(tx) {
        tx.executeSql("SELECT COUNT(*) FROM WebkitStickyNotes", [], function(result) {
            loadNotes();
        }, function(tx, error) {
            tx.executeSql("CREATE TABLE WebKitStickyNotes (id REAL UNIQUE, note TEXT, timestamp REAL, left TEXT, top TEXT, zindex REAL)", [], function(result) {
                loadNotes();
            });
        });
    });
}

function loadNotes()
{
    db.transaction(function(tx) {
        tx.executeSql("SELECT id, note, timestamp, left, top, zindex FROM WebKitStickyNotes", [], function(tx, result) {
            for (var i = 0; i < result.rows.length; ++i) {
                var row = result.rows.item(i);
                var note = new Note();
                note.id = row['id'];
                note.text = row['note'];
                note.timestamp = row['timestamp'];
                note.left = row['left'];
                note.top = row['top'];
                note.zIndex = row['zindex'];

                if (row['id'] > highestId)
                    highestId = row['id'];
                if (row['zindex'] > highestZ)
                    highestZ = row['zindex'];
            }

            if (!result.rows.length)
                newNote();
        }, function(tx, error) {
            alert('Failed to retrieve notes from database - ' + error.message);
            return;
        });
    });
}

function modifiedString(date)
{
    return 'Last Modified: ' + date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
}

function newNote()
{
    var note = new Note();
    note.id = ++highestId;
    note.timestamp = new Date().getTime();
    note.left = Math.round(Math.random() * 400) + 'px';
    note.top = Math.round(Math.random() * 500) + 'px';
    note.zIndex = ++highestZ;
    note.saveAsNew();
}

if (db != null)
    addEventListener('load', loaded, false);

Link to download files:
https://www.dropbox.com/s/e6ucgle1a7...-menu.zip?dl=0
 
Last edited:
So I suppose a way to deal with this would be to only load the sticky note portion of the javascript once the right-click context menu has been selected/triggered.

I can divide my js code into the sticky note and context-menu portions (which is what I had originally anyway):

context-menu.js
stickies.js

Then remove the stickies.js from initially loading in the <head> of the html file.

Then how would I go about running my 'stickies.js code once I've right-clicked and selected "Add Note"?
 
This is a fairly common question. There are several ways to handle on load. The best in my opinion is to use CSS to initially visibility:hidden or display:none.
  • visibility:hidden; /*preserves the vertical and horizontal spacing of the hidden element*/
  • display:none; /*does NOT preserve the spacing, the element will expand when set to :block*/
For your code check:
Code:
function loadNotes(){...}

It looks like if the database returns no value then call newNote().
Code:
if (!result.rows.length)
                newNote();
 
This is a fairly common question. There are several ways to handle on load. The best in my opinion is to use CSS to initially visibility:hidden or display:none.
  • visibility:hidden; /*preserves the vertical and horizontal spacing of the hidden element*/
  • display:none; /*does NOT preserve the spacing, the element will expand when set to :block*/
For your code check:
Code:
function loadNotes(){...}

It looks like if the database returns no value then call newNote().
Code:
if (!result.rows.length)
                newNote();

Oh thank you thank you thank you! :)

I removed that section of code you highlighted and now it no longer loads the first sticky element.

I'm so grateful. You've no idea how much time and effort I've put into trying to solve this seemingly simple problem.

Now I just need to figure out how to place the creation of new notes near to the cursor (where I right-clicked) rather than randomly.
 
Last edited:
Oh thank you thank you thank you! :)
You are quite welcome. For perspective, yesterday I spent three hours tracking down a misspelled variable in javascript: colomns should have been columns in a 4000 line js file. Bug hunting is all part of the fun.

Now I just need to figure out how to place the creation of new notes near to the cursor (where I right-clicked) rather than randomly.
You will get it! I'll take a look at it again Monday, heading out on the boat this weekend.
 
Last edited:
You are quite welcome. For perspective, yesterday I spent three hours tracking down a misspelled variable in javascript: colomns should have been columns in a 4000 line js file. Bug hunting is all part of the fun.

You will get it! I'll take a look at it again Monday, heading out on the boat this weekend.
OK, so I've been struggling for a few days with this now. I've figured out that if I remove the following lines of code then the note position will no longer be random, but will be created in the top left corner:

Code:
note.left = Math.round(Math.random() * 400) + 'px';
note.top = Math.round(Math.random() * 400) + 'px';

I'd like it to be positioned where I've clicked the context menu "Add note".

I've tried a few things, but nothing is working so far. I guess I need to call the mouse position on click and use that as the co-ordinates for the new note? :confused:
 
I've tried a few things, but nothing is working so far. I guess I need to call the mouse position on click and use that as the co-ordinates for the new note? :confused:
Yes, that is what I would do. Create a couple of variables divide the new note height and width by 2 to find the center, then offset the x/y of the mouse click so that the center of the note appears right where the user clicks. I'd do some boundary checks as well to keep half the note from disappearing off the edges of the screen.
 
Yes, that is what I would do. Create a couple of variables divide the new note height and width by 2 to find the center, then offset the x/y of the mouse click so that the center of the note appears right where the user clicks. I'd do some boundary checks as well to keep half the note from disappearing off the edges of the screen.
Thank you for your help, although I think I've pretty much got to the limit of what I can do. It's just not happening lol
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.