Let's Make A Simple AJAX Note Taking App

Download

LINKTEXTIn this tutorial we will be making a simple app with PHP and jQuery that lets users write notes. The notes are going to be saved in plain text files on the server. It demonstrates how to read and write files in PHP, how to resize a textarea with jQuery, depending on the text inside it, and how to create a simple AJAX interaction.

Grab a copy of the demo from the button above and read on!

The HTML

To start off, we need to create a regular HTML5 document. I have including only the important bits below, but you can see the rest in index.php. Notice that I have placed the PHP code in the same file for simplicity.

index.php

<div id="pad">
    <h2>Note</h2>
    <textarea id="note"><?php echo $note_content ?></textarea>
</div>

That is all the markup that we need for the note! Of course, we will be heavily styling it with CSS in a few minutes. I've also included the jQuery library further down the page (before the closing body tag) along with our script.js file, but I won't be showing it here. The important thing is that PHP echo statement inside the textarea. It prints the last saved note of the user.

The PHP

The PHP code of the example is straightforward. What it does is to read and present the contents of the note on page load, and to write to it when an AJAX request is sent by jQuery. This will cause the note file to be overridden.

index.php

$note_name = 'note.txt';
$uniqueNotePerIP = true;

if($uniqueNotePerIP){

    // Use the user's IP as the name of the note.
    // This is useful when you have many people
    // using the app simultaneously.

    if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
        $note_name = 'notes/'.md5($_SERVER['HTTP_X_FORWARDED_FOR']).'.txt';
    }
    else{
        $note_name = 'notes/'.md5($_SERVER['REMOTE_ADDR']).'.txt';
    }
}

if(isset($_SERVER['HTTP_X_REQUESTED_WITH'])){
    // This is an AJAX request

    if(isset($_POST['note'])){
        // Write the file to disk
        file_put_contents($note_name, $_POST['note']);
        echo '{"saved":1}';
    }

    exit;
}

$note_content = '';

if( file_exists($note_name) ){
    $note_content = htmlspecialchars( file_get_contents($note_name) );
}

Notice the $uniqueNotePerIP variable. I am using this on the demo so that every user gets a unique note. This setting will cause each note to be saved with the visitor's IP address as a name. You can set it to false, if you want everyone to share a single note, but keep in mind that if two people edit the note simultaneously, the one that saves last will override it, and in rare cases the note itself may get corrupted.

Next up, the jQuery code!

illustration.jpg
AJAX Note Taking App

The jQuery

jQuery's job in this app, would be to listen for changes in the text area, and send them with an AJAX post request back to index.php, where the text is written to a file.

The usual approach would be to bind a handler to the keypress event, but in certain cases this won't be enough as the user may simply paste text into the textarea, choose an auto-correct suggestion by their browser or undo a change. Luckily for us, there is another event that handles all of these cases. It is the input event, which is supported by all modern browsers (read more here). You can see the code below.

assets/js/script.js

$(function(){

    var note = $('#note');

    var saveTimer,
        lineHeight = parseInt(note.css('line-height')),
        minHeight = parseInt(note.css('min-height')),
        lastHeight = minHeight,
        newHeight = 0,
        newLines = 0;

    var countLinesRegex = new RegExp('\n','g');

    // The input event is triggered on key press-es,
    // cut/paste and even on undo/redo.

    note.on('input',function(e){

        // Clearing the timeout prevents
        // saving on every key press
        clearTimeout(saveTimer);
        saveTimer = setTimeout(ajaxSaveNote, 2000);

        // Count the number of new lines
        newLines = note.val().match(countLinesRegex);

        if(!newLines){
            newLines = [];
        }

        // Increase the height of the note (if needed)
        newHeight = Math.max((newLines.length + 1)*lineHeight, minHeight);

        // This will increase/decrease the height only once per change
        if(newHeight != lastHeight){
            note.height(newHeight);
            lastHeight = newHeight;
        }
    }).trigger('input');    // This line will resize the note on page load

    function ajaxSaveNote(){

        // Trigger an AJAX POST request to save the note
        $.post('index.php', { 'note' : note.val() });
    }

});

Another useful thing that the above code does, is to count the number of new lines in the text and to enlarge the textarea automatically, depending on the value of the line-height CSS property.

And here is the CSS.

The CSS

In this section we will style the three elements you see in the HTML part of the tutorial. Each of the three elements you see there, are styled and added a background image. For the bottom part of the notepad, I am using an :after element. When the textarea is resized by jQuery, the bottom part is automatically pushed down.

assets/css/styles.css

#pad{
    position:relative;
    width: 374px;
    margin: 180px auto 40px;
}

#note{
    font: normal 15px 'Courgette', cursive;
    line-height: 17px;
    color:#444;
    background: url('../img/mid.png') repeat-y;
    display: block;
    border: none;
    width: 329px;
    min-height: 170px;
    overflow: hidden;
    resize: none;
    outline: 0px;
    padding: 0 10px 0 35px;
}

#pad h2{
    background: url('../img/header.png') no-repeat;
    overflow: hidden;
    text-indent: -9999px;
    height: 69px;
    position: relative;
}

#pad:after{
    position:absolute;
    content:'';
    background:url('../img/footer.png') no-repeat;
    width:100%;
    height:40px;
}

In addition, I have included the Courgette font from Google Web Fonts, which you can see referred in the #note block.

Done!

I hope that you liked this simple example and are full of ideas for improvements. If you need to support older IE versions that don't have the input event, I suggest you change the code so that the AJAX request is sent automatically with a timeout every 5 or 10 seconds.

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles

Yusuf YILDIZ

I want to do it something like this. I am on the front-end section and I want to make it with Ajax. Thank you for this tut :) http://yusuf.zamantunelim.com/dosyalar/gunlugum/

Martin Angelov

This is beautiful! The undo/redo won't be easy. The rest can be done by changing the styles of the textarea. What will the accept/remove buttons do in the lower right?

Hi, may I create a note taking app, something simular like this?

MrAtiebatie

I recognize the design of Forrst and I loved it, really cool to see it in action!

Nice work, Yusuf. Looks gorgeous.

Yusuf, neat work!

Another awesome tutorial. Thanks a lot!

ZergVanDerb

auto-height functionality would be awesome.

FINESTGOL

Hi, nice, but big code...(

Simon Clavey

Hey Martin,

From what I understood this data is being saved onto localstorage?

How hard would this be to save to a SQL database where it assigns a unique ID so you could auto-update whenever.

Martin Angelov

Nope, it is not saved to local storage, but in a text file on the server. It wouldn't be hard to store it into a database - you need to edit the code that saves the note. The ID part would be more tricky. You can use a get parameter like http://yoursite.com/notes/index.php?note=ID. If the visitor visits the app without that parameter, you generate the ID and redirect the user to that note.

jithin sha

can you write a tutorial to create a music player with jquery ,php and ajax. Plz

nice tuto brother , can't wait for next one :p

Wow, looks great. I just need to find a project to use it on :) Thanks Martin

Doesn't work in IPhone ... Maybe update .. ?

Can we please know on which licence are the images provided?
I lovet the note design (header, sheets, etc).
Thanks!

Martin Angelov

The design is taken from a free psd that is linked to in the demo (right below the notepad).

Dinesh Verma

Wow ... just bookmarked the demo page. It may prove helpful.

Alex Peta

Hi Martin, great post! I made an improvement to your notepad. The idea is to make it "self-expanding". I have blogged about it in the past, but to keep it short :

1.create a new hidden DIV element with the same css style (font, padding etc) like the text area.

  1. on input event , put the value of the textbox to the div, the div will self exapand verticall as it has fixed width.

  2. add the DIV height to the text area.

CSS changes :

#note, .note{
 //original code for #note here...
}
.note
{
    display    : block;
    resize     : none;
    white-space: pre-wrap;
    word-wrap  : break-word;
    z-index    : -9999;
}

HTML :

 
<div class="note" style="border:0px solid black;position:fixed;top:-9999px;left:-9999px;visibility:none;"></div>

JS :

$(function() {
    var note     = $('#note');
    var noteCopy = $('.note');

    note.on('input',function(e) {
       noteCopy.html(note.val());
       note.height(noteCopy.height());
    }).trigger('input');
});
Martin Angelov

Great solution! Certainly better than my '\n' counting trick as it doesn't account for wrapped extra long lines.

Hi, Great work first off.
Secondly, do you have any suggestions for altering this approach to make it essentially a 'minimal comment system' for multiple uses on the same page but for different posts or divs?

Using this tutorial's example, but having several 'notepads' relating to corresponding .txt documents?

cheers

Very good tutorial! Thanks ;-)

Hi Martin, Awesome Work.Thanks a lot for this tutorial, good job.

Thanks for this tutorial. A big Demo is here: write.fm (http://www.netzware.net/artikel/online-notizzettel-write-fm)

Awesome work, thanks so much for sharing!

Great tutorial! There is a bit of a problem security wise with the storage method. Could you store the notes folder outside the site root?

Thanks

Thanks a lot for the tutorial!

Is it possible to extend this to allow the user to enter his email address and email the notes to himself?

Hey Martin,

I downloaded the files, but I can't figure out where your html files are. I renamed the "index.php" to "index.html," but it doesn't seem to be behaving like the one in your demo.

Help please?

Hello security vulnerability! It is super easy to spoof the "IP address" and get the system to write to locations on the disk that you don't intend.

For example, using a web scraping toolkit, I can easily set a 'X_FORWARDED_FOR' HTTP header to '../../somedirectory/readme'. If the host has PHP warnings enabled, I could use that to discover directory structures on the server that may be exploited later. You incorrectly assume that all Internet citizens are good, well-behaved clients and individuals. Welcome to the Internet!

I love how your website has a demo page which executes this ill-conceived code. Remember, never trust any user input without sanitizing it first, which includes HTTP headers!

Martin Angelov

You are right! Such a rookie mistake. I updated the tutorial and demo with an md5() call to mitigate the attack.

I think this is really cool, but it can only save it into a TXT text, so why don't we make it read out? I want to say is, so that all users can see, its content and its author!

Awesome tutorial. This really helped me knock out some core concepts!