Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update jQuery plugin to build HTML and prevent it from rebuilding on multiple calls

I am working to convert my simple JavaScript Donut Chart into a jQuery Plugin.

It is my first jQuery plugin and I could use some help in a couple place please...

Demo of what I have so far: http://jsfiddle.net/jasondavis/qsgqebox/

Below is the JavaScript I have so far.

jQuery

jQuery.fn.updatePercentageGraph = function (options) {

    var settings = $.extend({
            // These are the defaults.
            percent: 0,
        }, options );    

    var percent = settings.percent;

    if(typeof percent === 'undefined') {
        var percent = parseInt(this.data('percent'));
    }else{
        if(percent === '') {
            var percent = parseInt(this.data('percent'));
        }else{
            var percent = parseInt(percent);
            this.attr('data-percent', percent);
        }
    }

    var deg = 360*percent/100;
    if (percent > 50) {
        this.addClass('gt-50');
    }else{
        this.removeClass('gt-50');
    }
    $('.ppc-progress-fill').css('transform','rotate('+ deg +'deg)');
    $('.ppc-percents span').html(percent+'%');

};

Usage:

$('#project_progress2').updatePercentageGraph(34);

What I need help with:

1)
It currently require the user to setup the HTML like this:

<div id="project_progress" class="progress-pie-chart" data-percent="40">
    <div class="ppc-progress">
        <div class="ppc-progress-fill4" style="transform: rotate(136.8deg);"></div>
    </div>
    <div class="ppc-percents4">
        <div class="pcc-percents-wrapper">
            <span>40%</span>
        </div>
    </div>
</div>

What I want to do though is make it where you can simply do something like this....

<div id="project_progress" class="progress-pie-chart" data-percent="40"></div>

and the jQuery plugin would then auto-create the proper child nodes.

The part jQuery would auto create:

<div class="ppc-progress">
    <div class="ppc-progress-fill4" style="transform: rotate(136.8deg);"></div>
</div>
<div class="ppc-percents4">
    <div class="pcc-percents-wrapper">
        <span>40%</span>
    </div>
</div>

2) Update some selectors to allow multiple Donut charts on page

Another issue in the plugin so far is the 3rd and 4th lines up from the bottom of the JavaScript shown above. It is calling $('.ppc-progress-fill') and $('.ppc-percents span') which would prevent the usage of multiple charts on the page so that need to be updated to target the class that exist in the current chart only.


3) Optimize to prevent the HTML structure from being re-created every time the function is called to update the percent value

If I convert this plugin to auto-generate the HTML structure, then I need to also consider optimizing it so that it doesn't have to keep on re-generating that structure over and over each time my code runs that will be calling $('#project_progress2').updatePercentageGraph(34); because in my project where I will use this, these charts will constantly be updating there percentage values.

Perhaps it might even be better to have a function that create the initial chart HTML and then a 2nd function that can be called over and over repeatedly that just handles the updating of the percentage and won't attempt to rebuild and inject the HTML structure over and over!


These issue I mention are the key areas I am a little stuck on at the moment and could use some help please.

JSFiddle Demo:

JSFiddle of what I have so far along with some buttons to test updating the percentage values: http://jsfiddle.net/jasondavis/qsgqebox/

like image 713
JasonDavis Avatar asked Sep 28 '22 08:09

JasonDavis


2 Answers

1) The part jQuery would auto create

To solve this point you need to create the HTML structure on plugin creation.

2) Update some selectors to allow multiple Donut charts on page

Prevent using whole page selectors. Instead, you can keep a reference to the created HTML structure and work only on this subset. Not the entire DOM.

3) Optimize to prevent the HTML structure from being re-created every time the function is called to update the percent value

A usual pattern to solve multiple instances is to create a data attribute that holds the plugin instance. If an instance already exists, then you just act on that instance instead of creating a new one.

Having this instance is also useful for another jQuery pattern for adding methods to plugins. You can check if the parameter passed to the plugin is a string and then call a method with that name.

$('#project_progress').updatePercentageGraph('percent', percent);

Also you can get the value using the same method without parameter:

var percent = $('#project_progress').updatePercentageGraph('percent');

All in one, I would suggest something like this:

(function ($, undefined) {
    'use strict';
    function PercentageGraph(element, options) {
        this.$percentageGraph = $(element).append(
            '<div class="ppc-progress">' +
                '<div class="ppc-progress-fill"></div>' +
            '</div>' +
            '<div class="ppc-percents">' +
                '<div class="pcc-percents-wrapper">' +
                    '<span>0%</span>' +
                '</div>' +
            '</div>'
        );
        this.options = $.extend({}, $(element).data(), options);
        this._init();
    }
    PercentageGraph.prototype = {
        _init: function () {
            this.percent(this.options.percent || 0);
        },
        _update: function () {
            var percent = this.options.percent;
            var deg = 360*percent/100;
            if (percent > 50) {
                this.$percentageGraph.addClass('gt-50');
            }else{
                this.$percentageGraph.removeClass('gt-50');
            }
            this.$percentageGraph.find('.ppc-progress-fill').css('transform','rotate('+ deg +'deg)');
            this.$percentageGraph.find('.ppc-percents span').html(percent+'%');
        },
        percent: function (percent) {
            // If no parameter, act as a getter. Otherwise act as a setter.
            if (percent === undefined) {
                return this.options.percent;
            } else {
                this.options.percent = percent;
                this._update();
            }
        },
    }

    $.fn.updatePercentageGraph = function (options) {
        var args = Array.prototype.slice.call(arguments, 1),
            result;
        this.each(function () {
            var plugin = $(this).data('percentage-graph');
            if (!plugin) {
                $(this).data('percentage-graph', (plugin = new PercentageGraph(this, options)));
            }
            var method = typeof options === 'string' ? options : '_init';
            result = plugin[method].apply(plugin, args);
            // Break the .each iteration if it is a getter, that is, when the method returns something.
            return result === undefined;
        });
        return result || this;
    };
}(jQuery));

See demo

like image 99
dreyescat Avatar answered Nov 04 '22 20:11

dreyescat


Am I crazy? Yes, I am!

Here you are http://jsfiddle.net/qsgqebox/3/

I'm not going to explain to you all this code. Just want to say that this is the way I usually code plugins for jQuery.

(function($) {
    'use strict';

    var PercentageGraph = function(element) {
        // Setup settings
        this.settings = $.extend({
            percent: element.data('percent') ? element.data('percent') : 0
        });

        // Store the given element
        this.element = element;

        // Create the progress bar
        this.create();

        // Initialization
        this.update();
    }

    PercentageGraph.prototype = {
        create: function() {
            // Create:
            // <div class="ppc-progress">
            //     <div class="ppc-progress-fill4"></div>
            // </div>
            this.progress = $('<div />');
            this.progress.addClass('ppc-progress');

            this.progressFill = $('<div />');
            this.progressFill
                .addClass('ppc-progress-fill')
                .appendTo(this.progress);

            // Create:
            // <div class="ppc-percents4">
            // <div class="pcc-percents-wrapper">
            //     <span>40%</span>
            // </div>
            // </div>
            this.percents = $('<div />');
            this.percents.addClass('ppc-percents');

            this.percentsWrapper = $('<div />');
            this.percentsWrapper
                .addClass('pcc-percents-wrapper')
                .appendTo(this.percents);

            this.percentsContent = $('<span />');
            this.percentsContent
                .text(this.settings.percent)
                .appendTo(this.percentsWrapper);

            // Append everything to the element
            this.progress.appendTo(this.element);
            this.percents.appendTo(this.element);
        },
        update: function(p) {
            var percent = p ? p : this.settings.percent,
                deg = 360 * percent / 100;

            if (percent > 50) {
                this.element.addClass('gt-50');
            } else {
                this.element.removeClass('gt-50');
            }

            this.progressFill.css('transform', 'rotate('+ deg +'deg)');
            this.percentsContent.html(percent + '%');
        }
    }

    jQuery.fn.percentageGraph = function() {
        return new PercentageGraph(this);
    }
})(jQuery);

(function($) {
    $(document).ready(function() {
        var progress = $('div#project_progress').percentageGraph();

        // Handle it
        $('.update-button').on('click', function() {
            var percent = $(this).data('percent');

            // Update it
            progress.update(percent);
        });        
    });
})(jQuery);
like image 44
Slimmi Avatar answered Nov 04 '22 21:11

Slimmi