Tuesday, April 5, 2016

Creating PDF using Pdfmake - Introduction

Pdfmake follows a declarative approach. You don't need to calculate positions manually like: writeText(text, x, y), moveDown etc..., as you other libraries do.

You just need to create document-definition-object like this:
var docDefinition = { content: 'This is an sample PDF printed with pdfMake' };

You can add more stuffs to the object with multi-level tables, images, lists, paragraphs, margins, styles etc.  After you made the object, you can create the pdf and download it.

// open the PDF in a new window
pdfMake.createPdf(docDefinition).open();
// print the PDF (not working in this version, will be added back in a couple of days)
// pdfMake.createPdf(docDefinition).print();
// download the PDF
pdfMake.createPdf(docDefinition).download();

Pdfmake has following features:
  • line-wrapping,
  • text-alignments (left, right, centered, justified),
  • numbered and bulleted lists,
  • tables and columns
    • auto/fixed/star-sized widths,
    • col-spans and row-spans,
    • headers automatically repeated in case of a page-break,
  • images and vector graphics,
  • convenient styling and style inheritance,
  • page headers and footers:
    • static or dynamic content,
    • access to current page number and page count,
  • background-layer
  • page dimensions and orientations,
  • margins,
  • custom page breaks,
  • font embedding,
  • support for complex, multi-level (nested) structures,
  • helper methods for opening/printing/downloading the generated PDF.
  • setting of PDF metadata (e.g. author, subject)
Source: 

Generating PDF using jsPDF - a whole example

Pdfmake vs jsPDF
There are 2 popular javascript tools to generate PDF on the client side. One is jsPDF, the other is pdfmake. The pdfmake is a layout engine on top of pdfkit, which supports a nice declarative json document description format. Pdfmake has better documentation, and jsPDF only have minimal document. jsPDF doesn't trim each line like Pdfmake does. Pdfmake supports new pages, which jsPDF doesn't. However pdfmake is still under beta version currently. The jsPDF has very simple documentation so be patient to apply it on complex project.

 jsPDF prints using:
    var doc = new jsPDF();
    doc.setFontSize(40);
    doc.text(35, 25, "Paranyan loves jsPDF");
    doc.addImage(imgData, 'JPEG', 15, 40, 180, 160);

Pdfmake on the other hand:
    var dd = {
        content: [
            'First paragraph',
            'Another paragraph, this time a little bit longer to make sure, this line will be divided into at least two lines'
        ]
    }

Note datatable javascript plugin applied pdfmake to export table data to pdf. So If your application used datatable pdf export feature, you don't need to add another javascript library.

The Flyer Builder Example
The flyer builder is a user interface applying jsPDF to allow the user to insert some basic data (a title, an abstract and a price). Optionally, an image can be added, otherwise a grey-boxed Special Offer title is displayed. This example is from this link and could be a very good start for learning how to generate PDF using jsPDF. 
There is one demo for this interface. Note the demo used iFrame to hold the interface and it only worked for Firefox.  

The PDF generation first creates a new instance of jsPDF object with these options: portrait orientation (p), millimeters units (mm),‘A4’ format.
var pdf = new jsPDF('p', 'mm', 'a4');
Images can be added using pdf.addImage  function. Note every object placed inside the pdf should be exactly positioned. Thus the coordinates of each object based on the unites defined above should be declared correctly.
// pdf.addImage(base64_source, image format, X, Y, width, height)
pdf.addImage(agency_logo.src, 'PNG', logo_sizes.centered_x, _y, logo_sizes.w, logo_sizes.h);
Also those images must be Base64 encoded: the agency logo is embedded in the script in this format, while the image loaded by the user is encoded using the readAsDataURL method in the $('#flyer-image').change listener.

The title is added using the textAlign function. Note that this function is not part of the jsPDF core, but, as suggested by the author in his examples, the library can be easily expanded using its API. You can find the textAlign() function at the top of flyer builder script:
pdf.textAlign(flyer_title, {align: "center"}, 0, _y);
This function calculates the X coordinate of the the text string to make it centered, and then calls the native text() method:
pdf.text(text string, X, Y);
To change text properties, you can use following methods:
setFontSize(), setFont(), setTextColor(), setFontType().
To set a 20pt Times Bold red string, for example, you need to type this:
pdf.setFontSize(20);
pdf.setFont("times");
pdf.setFontType("bold");
pdf.setTextColor(255, 0, 0);
pdf.text(10,10, 'This is a 20pt Times Bold red string');
The Special offer grey box and the price circle use two similar methods: roundedRect() and circle(). Both of them require top-left coordinates, size values (the width and height in the first case and the radius in the second one):
pdf.roundedRect( X, Y, width, height, radius along X axis, radius along Y axis, style);
pdf.circle( X, Y, radius, style);
The style parameters refers to the fill and stroke properties of the object. Valid styles are: S [default] for stroke, F for fill, and DF (or FD) for fill and stroke.

Fill and stroke properties must be set in advance using setFillColor and setDrawColor, which require a RGB value and setLineWidth that requires the line width value in the unit declared at inception of PDF document.

The full source code:
(function (API) {
    API.textAlign = function (txt, options, x, y) {
        options = options || {};
        // Use the options align property to specify desired text alignment
        // Param x will be ignored if desired text alignment is 'center'.
        // Usage of options can easily extend the function to apply different text
        // styles and sizes

        // Get current font size
        var fontSize = this.internal.getFontSize();

        // Get page width
        var pageWidth = this.internal.pageSize.width;

        // Get the actual text's width
        // You multiply the unit width of your string by your font size and divide
        // by the internal scale factor. The division is necessary
        // for the case where you use units other than 'pt' in the constructor
        // of jsPDF.
        var txtWidth = this.getStringUnitWidth(txt) * fontSize / this.internal.scaleFactor;

        if (options.align === "center") {
            // Calculate text's x coordinate
            x = (pageWidth - txtWidth) / 2;
        }
        else if (options.align === "centerAtX") { // center on X value
            x = x - (txtWidth / 2);
        }
        else if (options.align === "right") {
            x = x - txtWidth;
        }

        // Draw text at x,y
        this.text(txt, x, y);
    };
    /*
        API.textWidth = function(txt) {
         var fontSize = this.internal.getFontSize();
            return this.getStringUnitWidth(txt)*fontSize / this.internal.scaleFactor;
        };
    */
    API.getLineHeight = function (txt) {
        return this.internal.getLineHeight();
    };
})(jsPDF.API);

(function () {
    "use strict";
    /*
     Refs:
     http://mrrio.github.io/jsPDF/
     https://github.com/MrRio/jsPDF
     https://mrrio.github.io/jsPDF/doc/symbols/jsPDF.html
    */

    // some variables
    var agency_logo = {
      src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAEdCAMAAADD1fMAAAAAGXRFWHRTb2Z0d2' + 
           'FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABhQTFRFAHC6RpjOtdbr1uj0f7jd7/b7////IYLCvV4sVgAAIv' +
           '1JREFUeNrsnYvao6oOQE0I5v3feFqvqIQEqv61k5z9nT17tIrAIhcCdL1LiwTGR5UXAb3RWqTzKmgS6rqOwkMKG4m7Drz'
        w: 800,
        h: 285
    },

    agency_name = 'Travel & Holidays',
    agency_site_url = 'www.travelandholidays.com',
    footer = agency_name + ' - ' + agency_site_url,

    page_size = 'a4',
    page_width = 210, // mm
    page_margin = 10, // mm
    content_width = page_width - (page_margin * 2), // available width for the content

    _y, _x, // drawing coord
    color_array, _string, lineHeight, y_correction, // some variables
    vspace = 10 // standard vertical space between elements
    ;

    // some variables
    var can_display_preview = true, // if true a preview of the PDF can be displayed in the iframe,
        // this value is set to false if the browser can't display the preview
        preview_container = $('#pdf_preview'),
        update_preview_button = $('#flyer_preview_btn'),
        download_button = $('#flyer_download_btn');

    // preview can be displayed?
    if (navigator.msSaveBlob) { // older IE
        update_preview_button.prop('disabled', true);
        can_display_preview = false;
        preview_container.replaceWith(
          '<div class="no_iframe">' +
          '<div>' +
          "The preview can't be displayed" +
          '</div>' +
          '</div>'
        );
    }

    // utilities
    var hex2rgb = function (hex_string) {
        if (/^#/.test(hex_string)) {
            hex_string = hex_string.substr(1);
        }
        if (hex_string.length === 3) {
            hex_string = hex_string.replace(/\w/, function (match) {
                return String(match) + String(match);
            });
        }
        return {
            red: parseInt(hex_string.substr(0, 2), 16),
            green: parseInt(hex_string.substr(2, 2), 16),
            blue: parseInt(hex_string.substr(4, 2), 16)
        };
    }
    px2mm = function (pixel) {
        // px to inches
        var inches = pixel / 72;
        return inches * 25.4;
    },

    mm2px = function (millimeters) {
        // mm to inches
        var inches = millimeters / 25.4;
        return inches * 72;
    },

    // function to calculate and check img sizes
    imgSizes = function (img_w, img_h, img_mm_w) {
        /* img_w and img_h represent the original image size, in pixel
           img_mm_w is the desidered rendered image size, in millimeters */

        if (img_mm_w > content_width) { // this should be never used...
            img_mm_w = content_width;
        }

        if (mm2px(img_mm_w) > img_w) {
            throw 'The `img_mm_w` parameter is too big';
        }

        var img_mm_h = Math.round((px2mm(img_h) * img_mm_w) / px2mm(img_w));

        return {
            w: img_mm_w,
            h: img_mm_h,
            centered_x: (page_width - img_mm_w) / 2
        };
    };

    try {
        // image reading
        // More info at https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
        // for more examples about file api
        // take a look at https://scotch.io/tutorials/use-the-html5-file-api-to-work-with-files-locally-in-the-browser
        var flyer_img = $('#flyer-image'),
            img_data = null;

        $('#flyer-image').change(function () {
            // temporary disabling buttons while parsing image
            update_preview_button.prop('disabled', true);
            download_button.prop('disabled', true);
            /*
             getting the file
             flyer_img[0] : transforms the jQuery reference to a DOM object
             files[0]     : refers to the file the the user has chosen
             use `console.log(user_file);` to show some info about the file
            */
            var user_file = flyer_img[0].files[0];
            img_data = {
                type: user_file.type === 'image/jpeg' ? 'JPEG' : 'PNG' // maybe you should add some controls to prevent loading of other file types
            };

            var reader = new FileReader();
            reader.onload = function (event) {
                img_data.src = event.target.result;
                // we need this to get img dimensions
                var user_img = new Image();
                user_img.onload = function () {
                    img_data.w = user_img.width;
                    img_data.h = user_img.height;

                    // restoring buttons
                    download_button.prop('disabled', false);
                    if (can_display_preview) {
                        update_preview_button.prop('disabled', false);
                    }
                };
                user_img.src = img_data.src;
            };
            //when the file is read it triggers the onload event above.
            reader.readAsDataURL(user_file);
        });

        //!pdf builder
        var createPDF = function (update_preview) {
            /*
             update_preview:
              ==> true: shows pdf online
              ==> false: downloads the pdf
             */
            _y = page_margin; // vertical starting point

            // form data
            var flyer_title = $('#flyer-title').val(),
                flyer_title_size = $('#flyer-title-size').val(),
                flyer_title_color = $('#flyer-title-color').val(),
                flyer_description = $('#flyer-description').val(),
                flyer_price = $('#flyer-price').val(),
                flyer_price_currency = $('#flyer-price-currency').val(),
                flyer_price_color = $('#flyer-price-color').val();

            var pdf = new jsPDF('p', 'mm', page_size),
                text_baseline,
                // some colors:
                light_grey = 237,
                grey = 128,
                black = 0,
                white = 255;

            // Optional - set properties of the document
            pdf.setProperties({
                title: flyer_title,
                subject: footer,
                author: 'me',
                creator: 'Flyer Builder & jsPDF'
            });

            // !logo
            var logo_sizes = imgSizes(agency_logo.w, agency_logo.h, 60);
            pdf.addImage(agency_logo.src, 'PNG', logo_sizes.centered_x, _y, logo_sizes.w, logo_sizes.h);

            // fonts initializing
            pdf.setFont("helvetica");
            pdf.setFontType("bold");

            // !main title
            color_array = hex2rgb(flyer_title_color);
            pdf.setTextColor(color_array.red, color_array.green, color_array.blue);

            pdf.setFontSize(flyer_title_size);

            lineHeight = px2mm(pdf.getLineHeight(flyer_title));

            _y += (logo_sizes.h + vspace + lineHeight);

            pdf.textAlign(flyer_title, {
                align: "center"
            }, 0, _y);

            _y += vspace;

            // !user image
            if (img_data) {
                var img_sizes = imgSizes(img_data.w, img_data.h, content_width);
                pdf.addImage(img_data.src, img_data.type, img_sizes.centered_x, _y, img_sizes.w, img_sizes.h);
                _y += img_sizes.h;

            }
            else {
                // if we haven't an image, a grey box with a text is added
                var box_height = 80;

                pdf.setFillColor(light_grey);
                pdf.roundedRect(page_margin, _y, content_width, box_height, 5, 5, 'F');
                pdf.setFontSize(60);
                pdf.setTextColor(white);
                _string = 'SPECIAL OFFER';
                lineHeight = px2mm(pdf.getLineHeight(_string));

                // y_correction: value to be added to y coord of the grey box to have text vertically centered
                // it is empirically calculated adding 1/3 of text line height to half box height
                y_correction = box_height / 2 + lineHeight / 3;

                pdf.textAlign(_string, {
                    align: "center"
                }, 0, _y + y_correction);
                _y += box_height;
            }

            // !price
            // first: creating a circle that overlaps the bottom side of the image
            var circle_radius = 30;
            color_array = hex2rgb(flyer_price_color);
            pdf.setFillColor(color_array.red, color_array.green, color_array.blue);

            // _x and _y refer to center of the circle
            _x = content_width - circle_radius; // circle ends at `page_margin` millimeters from the image right side

            pdf.circle(_x, _y, circle_radius, 'F'); // circle overlaps image for 1/2 of its height
            pdf.setFontSize(60);
            pdf.setFont("times");
            pdf.setFontType("bold");

            _string = flyer_price_currency + parseInt(flyer_price); // decimals are removed
            lineHeight = px2mm(pdf.getLineHeight(_string));
            y_correction = lineHeight / 3;

            pdf.setTextColor(white);
            pdf.textAlign(_string, {
                align: "centerAtX"
            }, _x, _y + y_correction);

            // !description
            if (flyer_description) {
                pdf.setFontSize(20);
                pdf.setFont("helvetica");
                pdf.setFontType("italic");
                pdf.setTextColor(grey);

                var lineWidth = content_width - (circle_radius * 2) - (page_margin * 2);
                _y += page_margin;

                var line_height = 12; // mm

                var description_lines = pdf.splitTextToSize(flyer_description, lineWidth);
                //pdf.text(page_margin, _y, description_lines); // doesn't allows to change line spacing

                for (var i = 0; i < description_lines.length; i++) {
                    pdf.text(page_margin, _y, description_lines[i]);
                    _y += line_height;
                }
            }

            // !footer
            _y = 287;
            pdf.setFontSize(9);
            pdf.setFontType("normal");
            pdf.setTextColor(black);
            pdf.textAlign(footer, {
                align: "center"
            }, 0, _y);

            // ****************************
            // output
            if (update_preview) {
                preview_container.attr('src', pdf.output('datauristring'));
            }
            else {
                pdf.save('flyer ' + flyer_title + '.pdf');
            }
        }; // end createPDF

        // !buttons
        update_preview_button.click(function () {
            createPDF(true);
        });

        $('#flyer_download_btn').click(function () {
            createPDF(false);
        });
    }
    catch (e) {
        console.log(e);
    }
})();
You can check the demo for whole html, css, and javascript codes.

Source:

Wednesday, March 23, 2016

jQuery Plugin and special javascript literal

When you read javascript codes written by other developers, chances are those code may contain jQuery plugin codes. Usually those codes containing following format:
Type 1:
(function($) {
    $.fn.jPluginName = {
    },

    $.fn.jPluginName.defaults = {
    }
})(jQuery);
Type 2:
(function($) {
    $.jPluginName = {
    }
})(jQuery);
Type 3:
(function($){
    //Attach this new method to jQuery
    $.fn.extend({ 
        var defaults = {  
        }  
        var options =  $.extend(defaults, options);  
        //This is where you write your plugin's name
        pluginname: function() {
            //Iterate over the current set of matched elements
            return this.each(function() {
                //code to be inserted here
            });
        }
    }); 
})(jQuery);
A js code block looks like (function(){})()  actually defined a function and execute it. The parameter $ means the parameter is a reference. 

In jQuery, the fn property is just an alias to the prototype property.
So jQuery.fn === jQuery.prototype

Following is a simple javascript constructor function using prototype to extend the original class:
function Test() {
  this.a = 'a';
}
Test.prototype.b = 'b';

var test = new Test(); 
test.a; // "a", own property
test.b; // "b", inherited property
Following is a normal structure similar to the architecture of jQuery:
(function() {
  var foo = function(arg) { // core constructor
      // ensure to use the `new` operator
      if (!(this instanceof foo))
          return new foo(arg);
      // store an argument for this example
      this.myArg = arg;
      //..
  };

  // create `fn` alias to `prototype` property
  foo.fn = foo.prototype = {
      init: function () {/*...*/}
    //...
  };

  // expose the library
  window.foo = foo;
})();

// Extension:
foo.fn.myPlugin = function () {
    alert(this.myArg);
    return this; // return `this` for chainability
};

foo("bar").myPlugin(); // alerts "bar"
Type 1: This is actually not a plugin, it's an object passed as a function, and new functions added to the object using prototype.

Type 2: This is again not a plugin as it does not extend the $.fn object. It's just an extension of the jQuery core, although the outcome is the same. This is if you want to add traversing functions such as toArray and so on.

Type 3: This is the best method to add a plugin, the extended prototype of jQuery takes an object holding your plugin name and function and adds it to the plugin library for you.

Reference:

Tuesday, March 22, 2016

Applying Node.js in ASP.Net MVC 4.5

Node.js is a server side javascript framework which mainly used for real time web application. The core idea is event driving with asynchronous programming. When  you develop your dashboard web applications, it's so great to let your web page to show real time charts and statistic data. There is also real time programming framework named Signal R from Microsoft, which is purely under ASP.Net and a great contender compared with Node.js.

Compared with Signal R, Node.js has another feather that all javascript code you wrote for client side can be easily reused on the server side.  When I worked with a Online Web Reporting project, I applied Chart.js javascript to create vivid charts on the front web end, and also I applied jsPDF javascript library on client side to create PDF report for those vivid charts. All these worked fine but we also have a requirement that the server side can create same PDF reports and deliver them to our client's email. To make those PDF reports have same look and feel, the server side should call the same client side javascript code to create the PDF doc. The difference is the server side service will not be launched from client web side. And Node.js offered great chance to show their shinning part for sharing same client code on server side.

So we finally choose Node.js, to do above 2 great jobs.

The following blog link would compare Node.js and Signal R.
http://sim4all.com/blogging/?p=454

Monday, March 14, 2016

JQuery UI Drag and Drop Introduction

This is an interesting javascript drag and drop game.
<!doctype html>
<html lang="en">
<head>

<title>A jQuery Drag-and-Drop Number Cards Game</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link rel="stylesheet" type="text/css" href="style.css">

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.min.js"></script>
<script type="text/javascript">

var correctCards = 0;
$( init );

function init() {
  // Hide the success message
  $('#successMessage').hide();
  $('#successMessage').css( {
    left: '580px',
    top: '250px',
    width: 0,
    height: 0
  } );

  // Reset the game
  correctCards = 0;
  $('#cardPile').html( '' );
  $('#cardSlots').html( '' );

  // Create the pile of shuffled cards
  var numbers = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

  // Sort the array with a randomizing sort function to shuffle the numbers
  numbers.sort( function() { return Math.random() - .5 } );
  
  // loop through each element in the numbers array, creating a div card element
  // for each number. Inside the div we place the number, so that it appears on 
  // the card in the page.
  for ( var i=0; i<10; i++ ) {
    $('<div>' + numbers[i] + '</div>').data( 'number', numbers[i] )
      .attr('id', 'card'+numbers[i] ).appendTo( '#cardPile' ).draggable({
          containment: '#content',
          //to ensure that the dragged card always sits on top of the other cards
          stack: '#cardPile div',  
          cursor: 'move',

          //to make the card slide back to its initial position when dropped, 
          //so that the user can try again. When the card has been dragged to the correct
          //the option will be set as false.
          revert: true 
      });
  }

  // Create the card slots
  var words = [ 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 
'eight', 'nine', 'ten' ];

  for ( var i=1; i<=10; i++ ) {
    // store the card's number in a 'number' key inside the object by 
    // using jQuery's data() method.
    $('<div>' + words[i-1] + '</div>').data( 'number', i ).appendTo( 
'#cardSlots' ).droppable( {
      accept: '#cardPile div',
      hoverClass: 'hovered',
      drop: handleCardDrop
    } );
  }
}

function handleCardDrop( event, ui ) {
  var slotNumber = $(this).data( 'number' );
  var cardNumber = ui.draggable.data( 'number' );

  // If the card was dropped to the correct slot,
  // change the card colour, position it directly
  // on top of the slot, and prevent it being dragged
  // again
  if ( slotNumber == cardNumber ) {
    ui.draggable.addClass( 'correct' );
    ui.draggable.draggable( 'disable' );
    $(this).droppable( 'disable' );
    ui.draggable.position( { of: $(this), my: 'left top', at: 'left top' } );
    ui.draggable.draggable( 'option', 'revert', false );
    correctCards++;
  } 
  
  // If all the cards have been placed correctly then display a message
  // and reset the cards for another go
  if ( correctCards == 10 ) {
    $('#successMessage').show();
    $('#successMessage').animate( {
      left: '380px',
      top: '200px',
      width: '400px',
      height: '100px',
      opacity: 1
    } );
  }
}
</script>

<body>
<div class="wideBox">
  <h1>Drag-and-Drop with jQuery: Your Essential Guide</h1>
  <h2>A Number Cards Game</h2>
</div>

<div id="content">
  <div id="cardPile"> </div>
  <div id="cardSlots"> </div>

  <div id="successMessage">
    <h2>You did it!</h2>
    <button onclick="init()">Play Again</button>
  </div>
</div>
</body>
</html>
      
The HTML part:

In the head area we've included the jQuery and jQuery UI libraries, and linked to a style.css file. In the body we have:
  • A "#content" div to contain the game
  • A "#cardPile" div that will contain the set of 10 unsorted cards
  • A "#cardSlots" div that will contain the 10 slots to drop the cards into
  • A "#successMessage" div to display a "You did it!" message, along with a button to play again. 
The init() function:
  1. $( init ) is to call our init() function when the document is ready.
  2. To create the pile of cards, we first make an array, numbers, containing the numbers 1 to 10, then sort the array with a randomizing sort function to shuffle the numbers.
  3. Then loop through each element in the numbers array, creating a div card element for each number. Inside the div we place the number, so that it appears on the card in the page. Once we've created the divobject, we store the card's number in a 'number' key inside the object by using jQuery's data() method.
  4. Give the card div an id of 'cardn', where n is the card number. This lets us give each card a different colour in the CSS. We then append the card to the #cardPile div.
  5. The interesting bit in the loop is, of course, the call to the draggable()method. This:
    • Makes the card draggable
    • Uses containment to constrain it to the #content div
    • Uses stack to ensure that the dragged card always sits on top of the other cards
    • Uses cursor to turn the mouse cursor into a move cursor during the drag, and
    • Sets the revert option to true. This makes the card slide back to its initial position when dropped, so that the user can try again. We'll turn this option off when the user has dragged the card to the correct slot, as we'll see in a moment.
  6. The last part of the init() function creates the 10 slots to drag the cards onto. We create an array called words that contains the words "one" to "ten", then loop through the elements of the words array, creating a slot div for each number. As before, we use data() to record the number of the slot, so we can test if the user has dragged the correct card to the correct slot, and we append the slot to the #cardSlots div.
  7. This time, we call the droppable() method so that the slot can receive a draggable card. We use the accept option with the selector "#cardPile div" to ensure that the slot will only accept our number cards, and not any other draggable element. The hoverClass option adds a CSS class to a droppable when a draggable is hovered over it — we use this option to add a class of 'hovered' to the element, which we'll highlight using CSS. Finally, we set up a drop event handler called handleCardDrop(), which is triggered when the user drops a card on the droppable. We'll write this handler next.
The CSS Part: 
body {

  margin: 30px;

  font-family: "Georgia", serif;

  line-height: 1.8em;

  color: #333;

}



/* Give headings their own font */



h1, h2, h3, h4 {

  font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;

}



/* Main content area */



#content {

  margin: 80px 70px;

  text-align: center;

  -moz-user-select: none;

  -webkit-user-select: none;

  user-select: none;

}



/* Header/footer boxes */



.wideBox {

  clear: both;

  text-align: center;

  margin: 70px;

  padding: 10px;

  background: #ebedf2;

  border: 1px solid #333;

}



.wideBox h1 {

  font-weight: bold;

  margin: 20px;

  color: #666;

  font-size: 1.5em;

}



/* Slots for final card positions */



#cardSlots {

  margin: 50px auto 0 auto;

  background: #ddf;

}



/* The initial pile of unsorted cards */



#cardPile {

  margin: 0 auto;

  background: #ffd;

}



#cardSlots, #cardPile {

  width: 910px;

  height: 120px;

  padding: 20px;

  border: 2px solid #333;

  -moz-border-radius: 10px;

  -webkit-border-radius: 10px;

  border-radius: 10px;

  -moz-box-shadow: 0 0 .3em rgba(0, 0, 0, .8);

  -webkit-box-shadow: 0 0 .3em rgba(0, 0, 0, .8);

  box-shadow: 0 0 .3em rgba(0, 0, 0, .8);

}



/* Individual cards and slots */



#cardSlots div, #cardPile div {

  float: left;

  width: 58px;

  height: 78px;

  padding: 10px;

  padding-top: 40px;

  padding-bottom: 0;

  border: 2px solid #333;

  -moz-border-radius: 10px;

  -webkit-border-radius: 10px;

  border-radius: 10px;

  margin: 0 0 0 10px;

  background: #fff;

}



#cardSlots div:first-child, #cardPile div:first-child {

  margin-left: 0;

}



#cardSlots div.hovered {

  background: #aaa;

}



#cardSlots div {

  border-style: dashed;

}



#cardPile div {

  background: #666;

  color: #fff;

  font-size: 50px;

  text-shadow: 0 0 3px #000;

}



#cardPile div.ui-draggable-dragging {

  -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);

  -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);

  box-shadow: 0 0 .5em rgba(0, 0, 0, .8);

}



/* Individually coloured cards */



#card1.correct { background: red; }

#card2.correct { background: brown; }

#card3.correct { background: orange; }

#card4.correct { background: yellow; }

#card5.correct { background: green; }

#card6.correct { background: cyan; }

#card7.correct { background: blue; }

#card8.correct { background: indigo; }

#card9.correct { background: purple; }

#card10.correct { background: violet; }





/* "You did it!" message */

#successMessage {

  position: absolute;

  left: 580px;

  top: 250px;

  width: 0;

  height: 0;

  z-index: 100;

  background: #dfd;

  border: 2px solid #333;

  -moz-border-radius: 10px;

  -webkit-border-radius: 10px;

  border-radius: 10px;

  -moz-box-shadow: .3em .3em .5em rgba(0, 0, 0, .8);

  -webkit-box-shadow: .3em .3em .5em rgba(0, 0, 0, .8);

  box-shadow: .3em .3em .5em rgba(0, 0, 0, .8);

  padding: 20px;

}

Source: http://www.elated.com/articles/drag-and-drop-with-jquery-your-essential-guide/
Game Link: http://www.elated.com/res/File/articles/development/javascript/jquery/drag-and-drop-with-jquery-your-essential-guide/card-game.html

Monday, February 1, 2016

Extending Chartjs


ChartJs is a free Javascript Chart library.Here is one example to extend the line chart with null data value.
    var ctx = document.getElementById("chart").getContext("2d");
    var data = {
        labels: ["January", "February", "March", "April", "May", "June", "July"],
        datasets: [{
                label: "My First dataset",
                fillColor: "rgba(220,220,220,0.2)",
                strokeColor: "rgba(220,220,220,1)",
                pointColor: "rgba(220,220,220,1)",
                pointStrokeColor: "#fff",
                pointHighlightFill: "#fff",
                pointHighlightStroke: "rgba(220,220,220,1)",
                data: [65, null, null, 81, 56, 55, 40]
            },
        ]
    };

    Chart.types.Line.extend({
        // Passing in a name registers this chart in the Chart namespace in the same way
        name: "MissingLine",
        initialize: function(data) {
            var helpers = Chart.helpers;
            //Declare the extension of the default point, to cater for the options passed in to the constructor
            this.PointClass = Chart.Point.extend({
                strokeWidth: this.options.pointDotStrokeWidth,
                radius: this.options.pointDotRadius,
                display: this.options.pointDot,
                hitDetectionRadius: this.options.pointHitDetectionRadius,
                ctx: this.chart.ctx,
                inRange: function(mouseX) {
                    return (Math.pow(mouseX - this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius, 2));
                }
            });

            this.datasets = [];

            //Set up tooltip events on the chart
            if (this.options.showTooltips) {
                helpers.bindEvents(this, this.options.tooltipEvents, function(evt) {
                    var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
                    this.eachPoints(function(point) {
                        point.restore(['fillColor', 'strokeColor']);
                    });
                    helpers.each(activePoints, function(activePoint) {
                        activePoint.fillColor = activePoint.highlightFill;
                        activePoint.strokeColor = activePoint.highlightStroke;
                    });
                    this.showTooltip(activePoints);
                });
            }

            //Iterate through each of the datasets, and build this into a property of the chart
            helpers.each(data.datasets, function(dataset) {

                var datasetObject = {
                    label: dataset.label || null,
                    fillColor: dataset.fillColor,
                    strokeColor: dataset.strokeColor,
                    pointColor: dataset.pointColor,
                    pointStrokeColor: dataset.pointStrokeColor,
                    points: []
                };

                this.datasets.push(datasetObject);

                helpers.each(dataset.data, function(dataPoint, index) {
                    /***
                     * Check for datapoints that are null
                     ***/
                    if (helpers.isNumber(dataPoint) || dataPoint === null) {
                        //Add a new point for each piece of data, passing any required data to draw.
                        datasetObject.points.push(new this.PointClass({
                            /*** 
                             * add ignore field so we can skip them later
                             ***/
                            ignore: dataPoint === null,
                            value: dataPoint,
                            label: data.labels[index],
                            datasetLabel: dataset.label,
                            strokeColor: dataset.pointStrokeColor,
                            fillColor: dataset.pointColor,
                            highlightFill: dataset.pointHighlightFill || dataset.pointColor,
                            highlightStroke: dataset.pointHighlightStroke || dataset.pointStrokeColor
                        }));
                    }
                }, this);

                this.buildScale(data.labels);

                this.eachPoints(function(point, index) {
                    helpers.extend(point, {
                        x: this.scale.calculateX(index),
                        y: this.scale.endPoint
                    });
                    point.save();
                }, this);

            }, this);

            this.render();
        },

        draw: function(ease) {
            var helpers = Chart.helpers;
            var easingDecimal = ease || 1;
            this.clear();

            var ctx = this.chart.ctx;

            this.scale.draw(easingDecimal);

            helpers.each(this.datasets, function(dataset) {
                //Transition each point first so that the line and point drawing isn't out of sync
                //We can use this extra loop to calculate the control points of this dataset also in this loop

                helpers.each(dataset.points, function(point, index) {
                    point.transition({
                        y: this.scale.calculateY(point.value),
                        x: this.scale.calculateX(index)
                    }, easingDecimal);

                }, this);

                // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
                // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
                if (this.options.bezierCurve) {
                    helpers.each(dataset.points, function(point, index) {
                        //If we're at the start or end, we don't have a previous/next point
                        //By setting the tension to 0 here, the curve will transition to straight at the end
                        if (index === 0) {
                            point.controlPoints = helpers.splineCurve(point, point, dataset.points[index + 1], 0);
                        } else if (index >= dataset.points.length - 1) {
                            point.controlPoints = helpers.splineCurve(dataset.points[index - 1], point, point, 0);
                        } else {
                            point.controlPoints = helpers.splineCurve(dataset.points[index - 1], point, dataset.points[index + 1], this.options.bezierCurveTension);
                        }
                    }, this);
                }

                //Draw the line between all the points
                ctx.lineWidth = this.options.datasetStrokeWidth;
                ctx.strokeStyle = dataset.strokeColor;

                var penDown = false;
                var start = null

                helpers.each(dataset.points, function(point, index) {
                    /***
                     * no longer draw if the last point was ignore (as we don;t have anything to draw from)
                     * or if this point is ignore
                     * or if it's the first
                     ***/
                    if (!point.ignore && !penDown) {
                        ctx.beginPath();
                        penDown = true;
                        start = point;
                    }
                    if (index > 0 && !dataset.points[index - 1].ignore && !point.ignore) {
                        if (this.options.bezierCurve) {
                            ctx.bezierCurveTo(
                                dataset.points[index - 1].controlPoints.outer.x,
                                dataset.points[index - 1].controlPoints.outer.y,
                                point.controlPoints.inner.x,
                                point.controlPoints.inner.y,
                                point.x,
                                point.y
                            );
                        } else {
                            ctx.lineTo(point.x, point.y);
                        }
                    } else if (index === 0 || dataset.points[index - 1].ignore) {
                        ctx.moveTo(point.x, point.y);
                    }

                    if (((dataset.points.length > index + 1 && dataset.points[index + 1].ignore) ||
                        dataset.points.length == index + 1) && !point.ignore) {
                        ctx.stroke();

                        if (this.options.datasetFill) {
                            ctx.lineTo(point.x, this.scale.endPoint);
                            ctx.lineTo(start.x, this.scale.endPoint);
                            ctx.fillStyle = dataset.fillColor;
                            ctx.closePath();
                            if (point.x != start.x) {
                                ctx.fill();
                            }
                        }
                        penDown = false;
                    }

                }, this);


                //Now draw the points over the line
                //A little inefficient double looping, but better than the line
                //lagging behind the point positions
                helpers.each(dataset.points, function(point) {
                    /**
                     * don't draw the dot if we are ignoring
                     */
                    if (!point.ignore)
                        point.draw();
                });

            }, this);
        }
    });

    var myLineChart = new Chart(ctx).MissingLine(data);
Resources:
http://jsfiddle.net/leighking2/sLgefm04/6/