codenothing/CSSCompressor

View on GitHub
demo/js/home.js

Summary

Maintainability
C
1 day
Test Coverage
/*
 * WARNING! This control file is a giant mess. Read at your own risk.
 * TODO: Organize this file
 */
function prettySettingsJSON( settings ) {
    var str = "{\n\t\"format\": \"" + settings.format + "\"";
    delete settings.format;

    jQuery.each( settings, function( name, value ) {
        var option = CSSCompressor.rule[ name ];
        str += ",\n\n\t// " + option.description + "\n";
        str += "\t\"" + name + "\": " + ( value ? 'true' : 'false' );
    });

    return str += "\n}";
}

// Quick jQuery function to select a portion of a textbox,
// and scroll the textbox to the selected position
jQuery.fn.textSelection = function( start, end ) {
    return this.each(function( i, element ) {
        var textarea = jQuery( element ),
            height = textarea.height(),
            current, caret, ypos, range;

        // Cross browser selection
        if ( start >= end ) {
            alert( 'Start Must Be Less Than End' );
            return;
        }
        else if ( element.createTextRange ) {
            range = element.createTextRange();
            range.collapse( true );
            range.moveStart( 'character', start );
            range.moveEnd( 'character', end );
            range.select();
        }
        else if ( element.setSelectionRange ) {
            element.setSelectionRange( start, end );
        }
        else if ( element.selectionStart ) {
            element.selectionStart = start;
            element.selectionEnd = end;
        }

        // Scroll textarea to bring the selection block into view
        current = textarea.scrollTop();
        caret = textarea.textareaHelper( 'caretPos' ).top;
        ypos = current + caret - parseInt( height / 2, 10 );
        if ( ypos < 0 ) {
            ypos = 0;
        }
        textarea.scrollTop( ypos );
    });
};

jQuery(function( jQuery ) {
    var optionWrapper = jQuery( 'section.options' ),
        resultWrapper = jQuery( 'section.results' ),
        modeWrapper = jQuery( '.widget-mode' ),
        modeContent = modeWrapper.find( 'textarea' ),
        rulesWrapper = jQuery( '.widget-rules' ),
        rulesDoc = jQuery( '.widget-rules-doc' ),
        options = optionWrapper.find( 'ul' ),
        modes = optionWrapper.find( 'select[name=modes]' ),
        format = optionWrapper.find( 'select[name=format]' ),
        fileInput = resultWrapper.find( 'input[type=file]' ),
        input = resultWrapper.find( 'textarea.input' ),
        result = resultWrapper.find( 'textarea.output' ),
        logs = resultWrapper.find( 'ul.logs' ),
        runButton = resultWrapper.find( 'button' ),
        statsTab = resultWrapper.find( '.stats-tab' ),
        statsTable = resultWrapper.find( '.stats-results table' ),
        statsTime = resultWrapper.find( '.stats-results .stats-time' ),
        html = '', groups = {};
    
    // Attach current version number to header
    jQuery( 'h1' ).html( 'CSS Compressor ' + CSSCompressor.version );

    // Group items
    CSSCompressor.rule().forEach(function( rule ) {
        if ( ! groups[ rule.group ] ) {
            groups[ rule.group ] = [];
        }

        groups[ rule.group ].push( rule );
    });

    // List Items
    CSSCompressor.each( groups, function( group, name ) {
        if ( group.length ) {
            html += [ "<li class='group'>" + name + "</li>" ];
            group.forEach(function( rule ) {
                html += [
                    "<li data-key='" + rule.name + "'>",
                        "<label>",
                            "<input type='checkbox' name='" + rule.name + "' />",
                            "<div class='input-content'>",
                                "<div class='name'>" + rule.name + "</div>",
                                "<div class='description'>" + rule.description + "</div>",
                            "</div>",
                        "</label>",
                    "</li>"
                ].join( '' );
            });
        }
    });
    html += "<li class='rules'><a href='rules/' target='_blank'>All Rules</a></li>";
    options.html( html );

    // Mode selections
    html = "<option value='custom'>Custom</option>";
    CSSCompressor.each( CSSCompressor.mode, function( mode, name ) {
        var display = name[ 0 ].toUpperCase() + name.substr( 1 );
        html += [
            "<option value='" + name + "'>" + display + "</option>"
        ].join( '' );
    });
    modes.html( html );

    // Readability
    html = "";
    CSSCompressor.each( CSSCompressor.format, function( format, name ) {
        var display = name[ 0 ].toUpperCase() + name.substr( 1 );
        html += [
            "<option value='" + name + "'>" + display + "</option>"
        ].join( '' );
    });
    format.html( html ).val( CSSCompressor.FORMAT_NONE );

    // Add handles to work with mode view overlay
    optionWrapper.find( 'span.mode-view' ).on( 'click', function( event ) {
        modeWrapper.removeClass( 'hide' );
        modeContent.val( prettySettingsJSON( getOptions() ) );
    });
    modeWrapper.find( '.close-button' ).on( 'click', function(){
        modeWrapper.addClass( 'hide' );
    });

    // Show rules when tapping logs rule
    logs.on( 'click', 'strong > span', function(){
        var key = jQuery( this ).attr( 'data-key' );

        if ( key.length && CSSCOMPRESSOR_RULE_DOCS[ key ] ) {
            rulesWrapper.removeClass( 'hide' );
            rulesDoc.html( CSSCOMPRESSOR_RULE_DOCS[ key ] );
            Rainbow.color();
        }
    });

    // Show rules when clicking on options name
    options.on( 'click', '.name', function(){
        var key = jQuery( this ).parents( 'li' ).attr( 'data-key' );

        if ( key.length && CSSCOMPRESSOR_RULE_DOCS[ key ] ) {
            rulesWrapper.removeClass( 'hide' );
            rulesDoc.html( CSSCOMPRESSOR_RULE_DOCS[ key ] );
            Rainbow.color();
            return false;
        }
    });

    // Hide rules on close button click
    rulesWrapper.find( '.close-button' ).on( 'click', function(){
        rulesWrapper.addClass( 'hide' );
    });

    // Close modals on escape
    jQuery( document ).on( 'keyup', function( event ) {
        if ( event.keyCode == 27 ) {
            modeWrapper.addClass( 'hide' );
            rulesWrapper.addClass( 'hide' );
        }
    });


    // Toggle run button based on content
    function runButtonToggle(){
        if ( ( input.val() || '' ).length || fileInput[ 0 ].files.length ) {
            runButton.addClass( 'enabled' );
        }
        else {
            runButton.removeClass( 'enabled' );
        }
    }

    // Enable run button once there is input
    input.on( 'keyup', runButtonToggle );
    fileInput.on( 'change', runButtonToggle );

    // Mode Change
    modes.on( 'change', function(){
        var mode = CSSCompressor.mode[ modes.val() || '' ];

        if ( mode ) {
            options.children( 'li:not(.group)' ).each(function( i, elem ) {
                var li = jQuery( elem ),
                    checkbox = li.find( 'input[type=checkbox]' )[ 0 ];

                if ( checkbox ) {
                    checkbox.checked = !!mode[ li.attr( 'data-key' ) || '' ];
                }
            });
        }
    })
    .val( CSSCompressor.MODE_DEFAULT )
    .trigger( 'change' );

    // Switching to custom compression level when changes are made
    options.delegate( 'input[type=checkbox]', 'change', function(){
        modes.val( 'custom' );
    });

    // Log tapping
    logs.delegate( 'a', 'click', function( event ) {
        var element = jQuery( this ),
            logIndex = parseInt( element.attr( 'data-logid' ) || '', 10 ),
            index = parseInt( element.attr( 'data-index' ) || '', 10 ),
            log = CSSCompressor._lastRun.log()[ logIndex ] || {},
            position = ( log.positions || [] )[ index ];

        if ( position && ( position.range.end - position.range.start ) < ( input.val() || '' ).length ) {
            input.textSelection( position.range.start, position.range.end + 1 );
        }
    });

    // Display string for size
    function sizeDisplay( size ) {
        var suffixs = [ 'B', 'K', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ], i = 0;

        for ( ; size > 1024; i++ ) {
            size /= 1024;
        }

        return ( Math.floor( size * 100 ) / 100 ) + suffixs[ i ];
    }

    // Grabs options
    function getOptions(){
        var settings = {
            format: format.val()
        };

        options.children( 'li' ).each(function( i, elem ) {
            var li = jQuery( elem ),
                key = li.attr( 'data-key' ),
                checkbox = li.find( 'input[type=checkbox]' )[ 0 ];

            if ( CSSCompressor.rule[ key ] ) {
                settings[ key ] = checkbox && checkbox.checked;
            }
        });

        return settings;
    }

    // Showing/Hiding stats
    statsTab.on( 'click', function(){
        resultWrapper.toggleClass( 'show-stats' );
    });

    // Reading individual files
    function readFile( file, callback ) {
        var reader = new FileReader();

        CSSCompressor.extend( reader, {

            onload: function( event ) {
                callback( null, event.target.result );
                callback = CSSCompressor.noop;
            },

            onerror: function( event ) {
                callback( event.target.error );
                callback = CSSCompressor.noop;
            }

        });

        reader.readAsText( file );
    }

    // Reading files from input
    function getFileString( callback ) {
        var files = fileInput[ 0 ].files || [], result = [], state = files.length;
        if ( ! files.length ) {
            return callback( null, '' );
        }

        CSSCompressor.each( files, function( file, i ) {
            result[ i ] = null;
            readFile( file, function( error, string ) {
                result[ i ] = string;
                state--;

                if ( error || state < 1 ) {
                    callback( error, result.join( '' ) );
                    callback = CSSCompressor.noop;
                }
            });
        });
    }

    // Run button
    runButton.on( 'click', function(){
        if ( ! runButton.hasClass( 'enabled' ) ) {
            return;
        }

        // Concat all css files
        getFileString(function( error, css ) {
            if ( error ) {
                alert( error );
                return;
            }

            // Generate options object to bind with compressor
            var compressor = new CSSCompressor( getOptions() ), html = '', stats = compressor.stats;
            css += input.val() || '';
            input.val( css );
            fileInput[ 0 ].value = '';

            // Output results
            result.val( compressor.compress( css ) );

            // Print out the logs
            html = "<li class='title-row'><div class='lines'>Lines</div><div class='message'>Log</div></li>";
            compressor.log().forEach(function( log, index ) {
                var lines = '', key = log.key ? "<strong>[<span data-key='" + log.key + "'>" + log.key + "</span>]</strong> " : '';

                if ( log.positions.length ) {
                    log.positions.forEach(function( position, i ) {
                        lines += "<a data-logid='" + index + "' data-index='" + i + "'>" + position.start.line + "</a>";
                        if ( i != log.positions.length - 1 ) {
                            lines += ',';
                        }
                    });
                }

                html += "<li data-id='" + index + "'>" +
                    "<div class='lines'>" + lines + "</div>" +
                    "<div class='message'>" + key + log.message + "</div>" +
                    "</li>";
            });
            logs.html( html );

            // Stats
            statsTime.html( "Time: " + stats.time.length + "ms" );
            statsTab.html(
                "Size: " +
                sizeDisplay( stats.after.size ) + ', Saved: ' +
                sizeDisplay( stats.before.size - stats.after.size ) +
                " (" + parseInt( ( ( stats.before.size - stats.after.size ) / stats.before.size ) * 100, 10 ) + "%)"
            );
            statsTable.html([
                "<tr><th></th><th>Before</th><th>Savings</th><th>Result</th></tr>",
                "<tr align='center'>",
                    "<th>Properties</th>",
                    "<td>" + stats.before.properties + "</td>",
                    "<td>" + ( stats.before.properties - stats.after.properties ) + "</td>",
                    "<td>" + stats.after.properties + "</td>",
                "</tr>",
                "<tr align='center'>",
                    "<th>Selectors</th>",
                    "<td>" + stats.before.selectors + "</td>",
                    "<td>" + ( stats.before.selectors - stats.after.selectors ) + "</td>",
                    "<td>" + stats.after.selectors + "</td>",
                "</tr>",
                "<tr align='center'>",
                    "<th>Size</th>",
                    "<td>" + sizeDisplay( stats.before.size ) + "</td>",
                    "<td>" + sizeDisplay( stats.before.size - stats.after.size ) + "</td>",
                    "<td>" + sizeDisplay( stats.after.size ) + "</td>",
                "</tr>"
            ].join( '' ));

            // Show results and shrink up the input box
            resultWrapper.addClass( 'ran' );
        });
    });
});