<commit_before>var http = require( 'http' ),
    formaline = require( '../../lib/formaline' ).formaline,
    connect = require( 'connect' ),
    fs = require( 'fs' ),
    server,
    log = console.log,
    dir =  '/tmp/';
    getHtmlForm = function ( req, res, next ) {
        if ( req.url === '/test/' ) {
        log( ' -> req url :', req.url );
        res.writeHead( 200, { 'content-type' : 'text/html' } );
        res.end( '\
                 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> \
                 \
                 \

\
                 Simple Url Encoded Post:

\
                 <form id="simple" action="/test/post" method="post">\
                 simplefield1 <input type="text" name="simplefield1" id="sfield1"/>
\
                 simplefield2 <input type="text" name="simplefield2" id="sfield2"/>
\
                 simplefield2 <input type="text" name="simplefield2" id="sfield3"/>
\
                 simplefield3 <input type="text" name="simplefield3" id="sfield4"/>
\
                 simplefield3 <input type="text" name="simplefield3" id="sfield5"/>
\
                 simplefield3 <input type="text" name="simplefield3" id="sfield6"/>
\
                 <input type="submit" value="Submit" id="submit1">\
                 </form>
\
                 Multiple File Upload:

\
                 <form id="mufile" action="/test/upload" enctype="multipart/form-data" method="post">\
                 demotitle1 <input type="text" name="demotitle1" id="id1"/>
\
                 multiplefield1 <input type="file" name="multiplefield1" multiple="multiple" id="id2">
\
                 demotitle2 <input type="text" name="demotitle2" id="id3">
\
                 multiplefield2 <input type="file" name="multiplefield2" multiple="multiple" id="id4">
\
                 demotitle3 <input type="text" name="demotitle3" id="id5">
\
                 multiplefield2 <input type="file" name="multiplefield3" multiple="multiple" id="id6">
\
                 <input type="submit" value="Upload" id="upload1"/>\
                 </form>
\
                 Iframe Multiple File Upload:

\
                 <form id="mframe" action="/test/upload" method="post" enctype="multipart/form-data" target="iframe" >\
                 iframefield1 <input type="text" name="iframefield1" id="ffield1"/>
\
                 iframefile1 <input type="file" name="iframefile1" multiple  src="" frameborder="1" id="ffield2"/>
\
                 iframefield2 <input type="text" name="iframefield2" id="ffield3"/>
\
                 iframefile2 <input type="file" name="iframefile2" multiple  src="" frameborder="1" id="ffield4"/>
\
                 iframefield2 <input type="text" name="iframefield2" id="ffield5"/>
\
                 iframefile2 <input type="file" name="iframefile2" multiple  src="" frameborder="1" id="ffield6"/>
\
                 <input type="submit" value="Upload" id="upload2"/>\
                 </form>\
                 \
                 </form>\
                 '
            );
        } else {
            next();
        }
    },
    handleFormRequest = function ( req, res, next ) {
        var receivedFields = {},
            form = null,
            currFile = null,
            config = {

                    // default is false -->

                holdFilesExtensions : !false,

                    // specify a path, with at least a trailing slash
                    // default is /tmp/ -->
                uploadRootDir : dir,

                    // default is false
                    // to create and check directories existence in the sync way
                mkDirSync : false,

                    // retrieve session ID for creating unique upload directory for authenticated users
                    // the upload directory gets its name from the returned session identifier,
                    // and will remain the same across multiple posts ( for the authenticated user with this session identifier )
                    // this function have to return the request property that holds session id
                    // the returned session id param, must contain a String, not a function or an object
                    // the function takes http request as a parameter at run-time

                getSessionID : function ( req ) {
                    return ( ( req.sessionID ) || ( req.sid ) || ( ( req.session && req.session.id ) ? req.session.id : null ) );
                },

                    // default is 120000 milliseconds ( default nodeJS timeout for connection requests )
                    // the client connection is closed after the specified milliseconds ( minimum is 100 millisecs )
                requestTimeOut : 5000, // 5 secs

                    // default is true
                    // when a fatal exception was thrown, the client request is resumed instead of immediately emitting 'loadend' event
                    // if false, the client request will be never resumed, the 'loadend' event will be emitted and the module doesn't handle the request anymore
                resumeRequestOnError : true,

                    // default is false
                    // return sha1 digests for files received?
                    // turn off for better perfomances
                sha1sum : false,

                    // switch on/off 'fileprogress' event
                    // default is false
                    // it serves to monitor the progress of the file upload
                    // and also to move the data to another stream, while the file is being uploaded
                emitFileProgress : false,

                    // switch on/off 'progress' event
                    // default is false, or integer chunk factor,
                    // every n chunk emits a dataprogress event:  1 + ( 0 * n ) 1 + ( 1 * n ), 1 + ( 2 * n ), 1 + ( 3 * n ),
                    // minimum factor value is 2
                emitProgress : false, // 3, 10, 100

                    // max bytes allowed for file uploads ( multipart/form-data ), it is a writing threshold, this is the max size of bytes written to disk before stopping
                uploadThreshold : 1024 * 1024 * 1024 ,// bytes

                    // max bytes allowed for serialized fields, it limits the parsing of data received with serialized fields ( x-www-urlencoded )
                    // when it was exceeded, no data was returned
                serialzedFieldThreshold : 1024 * 1024 * 1024,

                    // max bytes allowed for a single file
                maxFileSize : 1024 * 1024 * 1024, // bytes, default 1GB

                    // default is false, bypass content-length header value ( it must be present, otherwise an 'error'->'header' will be emitted ),
                    // also if it exceeds max allowable bytes; the module continues to write to disk until |uploadThreshold| bytes are written.
                    // if true ->  when headers content length exceeds uploadThreshold, module stops to receive data
                checkContentLength : false,

                    // default is false
                    // remove file not completed due to uploadThreshold,
                    // if true formaline emit fileremoved event,
                    // otherwise return a path array of incomplete files
                removeIncompleteFiles : false,

                    // default is 'debug:off,1:on,2:on,3:off,4:off,console:on,file:off,record:off';
                    // enable various logging levels
                    // it is possible to switch on/off one or more levels at the same time
                    // debug: 'off' turn off logging
                    // file: 'on' --> create a log file in the current upload directory with the same name and .log extension
                    // console: 'off' --> disable console log output
                    // record: 'on' --> record binary data from client request
                    // See the Readme!
                logging : 'debug:on,1:on,2:off,3:off,4:off,console:on,file:off,record:off', // <-- turn off 2nd level to see only warnings, and parser overall results

                    // listeners
                listeners : {
                    'message' : function ( json ) {
                    },
                    'error' : function ( json ) { // json:{ type: '..', isupload: true/false , msg: '..', fatal: true/false }
                    },
                    'abort' : function ( json ) {
                    },
                    'timeout' : function ( json ) {
                    },
                    'loadstart' : function ( json ){
                    },
                    'fileprogress' : function ( json, payload ) {
                        // json is the same for 'load' event ( when a file was received, see Readme ) ,
                        // 'payload' is a binary Buffer
                        // you can direct the data payload to another stream, while the file is being uploaded
                        /** /
                        if( currFile === null ) {
                          currFile = new fs.WriteStream( json.value.path + '*' );
                        }
                        currFile.write( payload );
                        /**/
                    },
                    'progress' : function ( json ) {
                    },
                    'load' : function ( json ){
                        currFile = null;
                    },
                    'loadend' : function ( json, res, next ) {
                        log( '\n\033[1;32mPost Done..\033[0m' );
                        // log( '\n JSON -> \n', json, '\n' );
                        res.writeHead( 200, { 'content-type' : 'text/plain' } );
                        res.write( '-> ' + new Date() + '\n' );
                        res.write( '-> request processed! \n' );
                        res.write( '\n-> stats -> ' + JSON.stringify( json.stats, null, 4 ) + '\n' );
                        res.write( '\n Initial Configuration : ' + JSON.stringify( form.initialConfig, function ( key, value ) {
                            if ( typeof value === 'function' ) {
                                return '..';
                            }
                            return value;
                        }, 4 ) + '\n' );

                        res.write( '\n-> fields received: [ { .. } , { .. } ] \n   ****************\n' + JSON.stringify( json.fields, null, 1 ) + '\n' );
                        res.write( '\n-> files written: [ { .. } , { .. } ] \n   **************\n ' + JSON.stringify( json.files, null, 1 ) + '\n' );
                        if ( form.removeIncompleteFiles ) {
                            res.write( '\n-> partially written ( removed ): [ { .. } , { .. } ] \n   *****************\n'+ JSON.stringify( json.incomplete, null, 1 ) + '\n' );
                        } else {
                            if ( json.incomplete.length !== 0 ) {
                                res.write( '\n-> partially written ( not removed ): \n   *****************\n' + JSON.stringify( json.incomplete, null, 1 ) + '\n' );
                            }
                        }
                        res.end();
                        next(); // test callback
                    }
                }
            };//end config obj

        if ( ( req.url === '/test/upload' ) || ( req.url === '/test/post' ) ) {
            log( ' -> req url :', req.url );
            form = new formaline( config ) ;
            form.parse( req, res, next );
        } else {
            log( ' -> req url 404 error :', req.url );
            res.writeHead( 404, { 'content-type': 'text/plain' } );
            res.end( '404' );
        }
};

server = connect( getHtmlForm , handleFormRequest, function () { form = null; console.log( '\n\033[1;33mHi!, I\'m the next() callback function!\033[0m' ); } );

server.listen( 3000 );

log();
log( ' ->\033[1m started at: \033[32m' + new Date() + '\033[0m' );
log( ' ->\033[1m listening on: \033[36mhttp://localhost:3000/\033[0m' );
log( ' ->\033[1m upload directory is: \033[31m' + dir + '\033[0m' );

<commit_msg>Removed annoying favicon.ico message from example modified:   examples/simple/simpleUpload.js<commit_after>var http = require( 'http' ),
    formaline = require( '../../lib/formaline' ).formaline,
    connect = require( 'connect' ),
    fs = require( 'fs' ),
    server,
    log = console.log,
    dir =  '/tmp/';
    getHtmlForm = function ( req, res, next ) {
        if ( req.url === '/test/' ) {
        log( ' -> req url :', req.url );
        res.writeHead( 200, { 'content-type' : 'text/html' } );
        res.end( '\
                 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> \
                 \
                 \

\
                 Simple Url Encoded Post:

\
                 <form id="simple" action="/test/post" method="post">\
                 simplefield1 <input type="text" name="simplefield1" id="sfield1"/>
\
                 simplefield2 <input type="text" name="simplefield2" id="sfield2"/>
\
                 simplefield2 <input type="text" name="simplefield2" id="sfield3"/>
\
                 simplefield3 <input type="text" name="simplefield3" id="sfield4"/>
\
                 simplefield3 <input type="text" name="simplefield3" id="sfield5"/>
\
                 simplefield3 <input type="text" name="simplefield3" id="sfield6"/>
\
                 <input type="submit" value="Submit" id="submit1">\
                 </form>
\
                 Multiple File Upload:

\
                 <form id="mufile" action="/test/upload" enctype="multipart/form-data" method="post">\
                 demotitle1 <input type="text" name="demotitle1" id="id1"/>
\
                 multiplefield1 <input type="file" name="multiplefield1" multiple="multiple" id="id2">
\
                 demotitle2 <input type="text" name="demotitle2" id="id3">
\
                 multiplefield2 <input type="file" name="multiplefield2" multiple="multiple" id="id4">
\
                 demotitle3 <input type="text" name="demotitle3" id="id5">
\
                 multiplefield2 <input type="file" name="multiplefield3" multiple="multiple" id="id6">
\
                 <input type="submit" value="Upload" id="upload1"/>\
                 </form>
\
                 Iframe Multiple File Upload:

\
                 <form id="mframe" action="/test/upload" method="post" enctype="multipart/form-data" target="iframe" >\
                 iframefield1 <input type="text" name="iframefield1" id="ffield1"/>
\
                 iframefile1 <input type="file" name="iframefile1" multiple  src="" frameborder="1" id="ffield2"/>
\
                 iframefield2 <input type="text" name="iframefield2" id="ffield3"/>
\
                 iframefile2 <input type="file" name="iframefile2" multiple  src="" frameborder="1" id="ffield4"/>
\
                 iframefield2 <input type="text" name="iframefield2" id="ffield5"/>
\
                 iframefile2 <input type="file" name="iframefile2" multiple  src="" frameborder="1" id="ffield6"/>
\
                 <input type="submit" value="Upload" id="upload2"/>\
                 </form>\
                 \
                 </form>\
                 '
            );
        } else {
            next();
        }
    },
    handleFormRequest = function ( req, res, next ) {
        var receivedFields = {},
            form = null,
            currFile = null,
            config = {

                    // default is false -->

                holdFilesExtensions : !false,

                    // specify a path, with at least a trailing slash
                    // default is /tmp/ -->
                uploadRootDir : dir,

                    // default is false
                    // to create and check directories existence in the sync way
                mkDirSync : false,

                    // retrieve session ID for creating unique upload directory for authenticated users
                    // the upload directory gets its name from the returned session identifier,
                    // and will remain the same across multiple posts ( for the authenticated user with this session identifier )
                    // this function have to return the request property that holds session id
                    // the returned session id param, must contain a String, not a function or an object
                    // the function takes http request as a parameter at run-time

                getSessionID : function ( req ) {
                    return ( ( req.sessionID ) || ( req.sid ) || ( ( req.session && req.session.id ) ? req.session.id : null ) );
                },

                    // default is 120000 milliseconds ( default nodeJS timeout for connection requests )
                    // the client connection is closed after the specified milliseconds ( minimum is 100 millisecs )
                requestTimeOut : 5000, // 5 secs

                    // default is true
                    // when a fatal exception was thrown, the client request is resumed instead of immediately emitting 'loadend' event
                    // if false, the client request will be never resumed, the 'loadend' event will be emitted and the module doesn't handle the request anymore
                resumeRequestOnError : true,

                    // default is false
                    // return sha1 digests for files received?
                    // turn off for better perfomances
                sha1sum : false,

                    // switch on/off 'fileprogress' event
                    // default is false
                    // it serves to monitor the progress of the file upload
                    // and also to move the data to another stream, while the file is being uploaded
                emitFileProgress : false,

                    // switch on/off 'progress' event
                    // default is false, or integer chunk factor,
                    // every n chunk emits a dataprogress event:  1 + ( 0 * n ) 1 + ( 1 * n ), 1 + ( 2 * n ), 1 + ( 3 * n ),
                    // minimum factor value is 2
                emitProgress : false, // 3, 10, 100

                    // max bytes allowed for file uploads ( multipart/form-data ), it is a writing threshold, this is the max size of bytes written to disk before stopping
                uploadThreshold : 1024 * 1024 * 1024 ,// bytes

                    // max bytes allowed for serialized fields, it limits the parsing of data received with serialized fields ( x-www-urlencoded )
                    // when it was exceeded, no data was returned
                serialzedFieldThreshold : 1024 * 1024 * 1024,

                    // max bytes allowed for a single file
                maxFileSize : 1024 * 1024 * 1024, // bytes, default 1GB

                    // default is false, bypass content-length header value ( it must be present, otherwise an 'error'->'header' will be emitted ),
                    // also if it exceeds max allowable bytes; the module continues to write to disk until |uploadThreshold| bytes are written.
                    // if true ->  when headers content length exceeds uploadThreshold, module stops to receive data
                checkContentLength : false,

                    // default is false
                    // remove file not completed due to uploadThreshold,
                    // if true formaline emit fileremoved event,
                    // otherwise return a path array of incomplete files
                removeIncompleteFiles : false,

                    // default is 'debug:off,1:on,2:on,3:off,4:off,console:on,file:off,record:off';
                    // enable various logging levels
                    // it is possible to switch on/off one or more levels at the same time
                    // debug: 'off' turn off logging
                    // file: 'on' --> create a log file in the current upload directory with the same name and .log extension
                    // console: 'off' --> disable console log output
                    // record: 'on' --> record binary data from client request
                    // See the Readme!
                logging : 'debug:on,1:on,2:off,3:off,4:off,console:on,file:off,record:off', // <-- turn off 2nd level to see only warnings, and parser overall results

                    // listeners
                listeners : {
                    'message' : function ( json ) {
                    },
                    'error' : function ( json ) { // json:{ type: '..', isupload: true/false , msg: '..', fatal: true/false }
                    },
                    'abort' : function ( json ) {
                    },
                    'timeout' : function ( json ) {
                    },
                    'loadstart' : function ( json ){
                    },
                    'fileprogress' : function ( json, payload ) {
                        // json is the same for 'load' event ( when a file was received, see Readme ) ,
                        // 'payload' is a binary Buffer
                        // you can direct the data payload to another stream, while the file is being uploaded
                        /** /
                        if( currFile === null ) {
                          currFile = new fs.WriteStream( json.value.path + '*' );
                        }
                        currFile.write( payload );
                        /**/
                    },
                    'progress' : function ( json ) {
                    },
                    'load' : function ( json ){
                        currFile = null;
                    },
                    'loadend' : function ( json, res, next ) {
                        log( '\n\033[1;32mPost Done..\033[0m' );
                        // log( '\n JSON -> \n', json, '\n' );
                        res.writeHead( 200, { 'content-type' : 'text/plain' } );
                        res.write( '-> ' + new Date() + '\n' );
                        res.write( '-> request processed! \n' );
                        res.write( '\n-> stats -> ' + JSON.stringify( json.stats, null, 4 ) + '\n' );
                        res.write( '\n Initial Configuration : ' + JSON.stringify( form.initialConfig, function ( key, value ) {
                            if ( typeof value === 'function' ) {
                                return '..';
                            }
                            return value;
                        }, 4 ) + '\n' );

                        res.write( '\n-> fields received: [ { .. } , { .. } ] \n   ****************\n' + JSON.stringify( json.fields, null, 1 ) + '\n' );
                        res.write( '\n-> files written: [ { .. } , { .. } ] \n   **************\n ' + JSON.stringify( json.files, null, 1 ) + '\n' );
                        if ( form.removeIncompleteFiles ) {
                            res.write( '\n-> partially written ( removed ): [ { .. } , { .. } ] \n   *****************\n'+ JSON.stringify( json.incomplete, null, 1 ) + '\n' );
                        } else {
                            if ( json.incomplete.length !== 0 ) {
                                res.write( '\n-> partially written ( not removed ): \n   *****************\n' + JSON.stringify( json.incomplete, null, 1 ) + '\n' );
                            }
                        }
                        res.end();
                        next(); // test callback
                    }
                }
            };//end config obj

        if ( ( req.url === '/test/upload' ) || ( req.url === '/test/post' ) ) {
            log( ' -> req url :', req.url );
            form = new formaline( config ) ;
            form.parse( req, res, next );
        } else {
            if ( req.url === '/favicon.ico' ) { // short-circuit annoying favicon requests
                res.writeHead( 200, { 'Content-Type' : 'image/x-icon' } );
                res.end();
            } else {
                log( ' -> req url 404 error :', req.url );
                res.writeHead( 404, { 'content-type' : 'text/plain' } );
                res.end( '404' );
            }
        }
};

server = connect( getHtmlForm , handleFormRequest, function () { form = null; console.log( '\n\033[1;33mHi!, I\'m the next() callback function!\033[0m' ); } );

server.listen( 3000 );

log();
log( ' ->\033[1m started at: \033[32m' + new Date() + '\033[0m' );
log( ' ->\033[1m listening on: \033[36mhttp://localhost:3000/\033[0m' );
log( ' ->\033[1m upload directory is: \033[31m' + dir + '\033[0m' );

<|endoftext|>


<commit_before>/*
The MIT License (MIT)

Copyright (c) 2013, Chris Staszkiewicz <chris.staszkiewicz@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

var Config = {
	Id: 'Twify-Tooltip',
	Attr: 'twify-tooltip',
	Padding: 7,
	Font: {
		Size: 12,
		Style: 'normal'
	},
	Color: '#FFF',
	Background: 'rgba(7,12,33,0.9)',
	Shadow: {
		Box: {
			Color: '#000',
			Size: [0, 0, 3]
		},
		Text: {
			Color: '#000',
			Size: [1, 1]
		}
	},
	Border: {
		Color: '#777777',
		Size: 1,
		Type: 'solid',
		Radius: 3
	}
}

var Twify = {
	Tooltip: function()
	{
		var Tooltip = this;
		this.__init__ = function()
		{
			$(document).ready(function()
			{
				$('body').prepend('

');

				$('#' + Config.Id).css({
					'display': 'none',
					'position': 'absolute',
					'z-index': 99999,
					'padding': Config.Padding,
					'background-color': Config.Background,
					borderRadius: Config.Border.Radius,
					'border': Config.Border.Size + 'px ' + Config.Border.Type + ' ' + Config.Border.Color,
					boxShadow: Config.Shadow.Box.Size.join('px ') + 'px ' + Config.Shadow.Box.Color,
					'font-size': Config.Font.Size + 'px',
					'font-style': Config.Font.Style,
					'color': Config.Color
				});

				Tooltip.addEvents();

				$(document).mousemove(function(e)
				{
					Tooltip.Move(e.pageX, e.pageY);
				});
			});
		}

		this.Move = function(x, y)
		{
			Id = '#' + Config.Id
			$(Id).css("left", x - ($(Id).css("width").replace("px", "") / 2)).css("top", y + 25);
		}

		this.addEvents = function()
		{
			$(document).on('mouseenter', '[' + Config.Attr + ']', function(event) {
				Tooltip.Show($(this).attr(Config.Attr));
			});
			$(document).on('mouseleave', '[' + Config.Attr + ']', function(event) {
				Tooltip.Hide();
			});
		}

		this.Show = function(data)
		{
			$('#' + Config.Id).html(data).show();
		}

		this.Hide = function()
		{
			$('#' + Config.Id).hide();
		}

		this.__init__();
	}
}
<commit_msg>Updated to jQuery plugin<commit_after>$.fn.TwifyTooltip = function(args)
{
	var Tooltip = this;
	var Config = {
		Id: 'Twify-Tooltip',
		Attr: 'twify-tooltip',
		Padding: 7,
		Font: {
			Size: 12,
			Style: 'normal'
		},
		Color: '#FFF',
		Background: 'rgba(7,12,33,0.9)',
		Shadow: {
			Box: {
				Color: '#000',
				Size: [0, 0, 3]
			},
			Text: {
				Color: '#000',
				Size: [1, 1]
			}
		},
		Border: {
			Color: '#777777',
			Size: 1,
			Type: 'solid',
			Radius: 3
		}
	}

	$.extend(Config, args);

	this.__init__ = function()
	{
		$(document).ready(function()
		{
			$(Tooltip.selector).prepend('

');

			$('#' + Config.Id).css({
				'display': 'none',
				'position': 'absolute',
				'z-index': 99999,
				'padding': Config.Padding,
				'background-color': Config.Background,
				borderRadius: Config.Border.Radius,
				'border': Config.Border.Size + 'px ' + Config.Border.Type + ' ' + Config.Border.Color,
				boxShadow: Config.Shadow.Box.Size.join('px ') + 'px ' + Config.Shadow.Box.Color,
				'font-size': Config.Font.Size + 'px',
				'font-style': Config.Font.Style,
				'color': Config.Color
			});

			Tooltip.addEvents();

			$(document).mousemove(function(e)
			{
				Tooltip.Move(e.pageX, e.pageY);
			});
		});
	}

	this.Move = function(x, y)
	{
		Id = '#' + Config.Id
		$(Id).css("left", x - ($(Id).css("width").replace("px", "") / 2)).css("top", y + 25);
	}

	this.addEvents = function()
	{
		$(Tooltip.selector).on('mouseenter', '[' + Config.Attr + ']', function(event) {
			Tooltip.Show($(this).attr(Config.Attr));
		});
		$(Tooltip.selector).on('mouseleave', '[' + Config.Attr + ']', function(event) {
			Tooltip.Hide();
		});
	}

	this.Show = function(data)
	{
		$('#' + Config.Id).html(data).show();
	}

	this.Hide = function()
	{
		$('#' + Config.Id).hide();
	}

	this.__init__();
};
<|endoftext|>


<commit_before>#!/usr/bin/env node

// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/**
 * COSI reset
 *
 * remove COSI checks, graphs, and worksheets
 *
 * use api to delete in circonus
 * remove local registration file
 * remove local config file
 * remove local template file
 *
 */

'use strict';

const path = require('path');
const fs = require('fs');
const Events = require('events').EventEmitter;
const child = require('child_process');

const app = require('commander');
const chalk = require('chalk');

const cosi = require(path.resolve(path.join(__dirname, '..', 'lib', 'cosi')));
const api = require(path.resolve(cosi.lib_dir, 'api'));

/**
 * delete a specific item
 * @arg {Object} item definition
 * @arg {Boolean} keepTemplates flag
 * @arg {Function} cb callback
 * @returns {Undefined} nothing
 */
function deleteItem(item, keepTemplates, cb) {
    let cfg = {};

    try {
        cfg = require(item.regFile); // eslint-disable-line global-require
    } catch (err) {
        if (err.code === 'MODULE_NOT_FOUND') {
            console.log(chalk.yellow('WARN'), 'Registration not found', item.regFile);

            cb(null);

            return;
        }

        cb(err);

        return;
    }

    const itemURL = cfg._cid;
    const itemName = cfg.display_name || cfg.title || cfg.description || '';

    const cleaner = () => {
        try {
            console.log(`\tremoving ${item.regFile}`);
            fs.unlinkSync(item.regFile);
        } catch (err) {
            return err;
        }

        if (item.cfgFile !== null) {
            try {
                console.log(`\tremoving ${item.cfgFile}`);
                fs.unlinkSync(item.cfgFile);
            } catch (err) {
                return err;
            }
        }

        if (item.templateFile !== null) {
            if (keepTemplates) {
                console.log(`\tKEEPING ${item.templateFile}`);
            } else {
                try {
                    console.log(`\tremoving ${item.templateFile}`);
                    fs.unlinkSync(item.templateFile);
                } catch (err) {
                    // graph templates can have mulitple graphs, ignore any missing files
                    if (err.code !== 'ENOENT') {
                        return err;
                    }
                }
            }
        }

        return null;
    };

    console.log(chalk.bold('Checking'), `${itemName} ${itemURL}`);
    api.get(itemURL, null).
        then((res) => {
            if (res.code === 404 && (res.raw_body && res.raw_body.indexOf('requested object was not found') !== -1)) {
                console.log(`\t${itemURL}`, chalk.bold('not found'), '- cleaning up orphaned files.');
                cb(cleaner());

                return;
            }

            if (res.code < 200 || res.code > 299) {
                console.error(chalk.red('API RESULT CODE'), res.code, res.parsed_body);
                cb(res.parsed_body);

                return;
            }

            console.log(chalk.bold('\tDeleting'), `${itemName} ${itemURL}`);

            api.delete(itemURL).
                then((res2) => {
                    if (res2.code < 200 || res2.code > 299) {
                        console.error(chalk.red('API RESULT CODE'), res2.code, res2.parsed_body);
                        cb(res2.parsed_body);

                        return;
                    }

                    cb(cleaner());
                }).
                catch((err) => {
                    console.error(chalk.red('API ERROR'), err);
                    console.error('Attempt to run the command again. Contact support if the error persists.');
                    process.exit(1);
                });
        }).
        catch((err) => {
            if ({}.hasOwnProperty.call(err, 'raw_body') && err.raw_body.indexOf('requested object was not found') !== -1) {
                console.log(`\t${itemURL}`, chalk.bold('not found'), '- cleaning up orphaned files.');
                cb(cleaner());

                return;
            }
            console.error(chalk.red('ERROR'), 'An API error occurred', err);
            cb(err);
        });
}

/**
 * find asset registrations
 * @arg {String} dir to search
 * @arg {String} itemType to search for
 * @arg {String} itemId of specific asset
 * @returns {Array} list of items
 */
function findItems(dir, itemType, itemId) {
    let id = null;

    if (typeof itemId === 'string') {
        id = itemId;
        if (id.substring(0, itemType.length + 1) !== `${itemType}-`) {
            id = `${itemType}-${id}`;
        }
        id = `registration-${id}`;
    } else {
        id = `registration-${itemType}-`;
    }

    const re = new RegExp(`^${id}.*`);

    let files = null;

    try {
        files = fs.readdirSync(dir);
    } catch (err) {
        throw err;
    }

    const entries = [];

    for (const file of files) {
        if (itemType === 'check' && typeof itemId === 'undefined') {
            // don't remove the group check unless it is explicitly
            // specified on the command line. (--check=group).
            // there is no way to know how many other systems
            // still be depend on the check.
            if (file.includes('-group')) {
                continue;
            }
        }

        if (file.match(re)) {
            const regFile = path.resolve(path.join(dir, file));
            const cfgFile = regFile.replace('registration-', 'config-');
            let templateFile = regFile.replace('registration-', 'template-');

            if (file.match('registration-graph')) {
                const parts = file.split('-');

                if (parts) {
                    templateFile = path.resolve(path.join(dir, `template-graph-${parts[2]}.json`));
                }
            }

            entries.push({
                cfgFile,
                regFile,
                templateFile
            });
        }
    }

    console.log(`Found ${entries.length} ${itemType}s.`);

    return entries;
}

/**
 * remove configuration files
 * @arg {Function} cb callback
 * @returns {Undefined} nothing
 */
function removeConfigs(cb) {
    console.log('Removing COSI configuration files');

    if (cosi.agent_mode === 'reverse') {
        console.log(`\tre-installing default NAD config`);
        try {
            child.execSync(path.resolve(path.join(cosi.cosi_dir, 'bin', 'nadreverse_uninstall.sh')));
        } catch (err) {
            console.dir(err);
        }
    }

    const configFiles = [
        path.resolve(path.join(cosi.etc_dir, 'cosi.json')),
        path.resolve(path.join(cosi.etc_dir, 'circonus-nadreversesh')),
        path.resolve(path.join(cosi.etc_dir, 'statsd.json')),
        path.resolve(path.join(cosi.cosi_dir, '..', 'etc', 'circonus-nadpush.json')),
        path.resolve(path.join(cosi.cosi_dir, '..', 'etc', 'pg-conf.sh')),
        path.resolve(path.join(cosi.cosi_dir, '..', 'etc', 'pg-po-conf.sh')),
        path.resolve(path.join(cosi.reg_dir, 'setup-config.json')),
        path.resolve(path.join(cosi.reg_dir, 'setup-metrics.json'))
    ];

    for (let i = 0; i < configFiles.length; i++) {
        const file = configFiles[i];

        try {
            console.log(`\tremoving ${file}`);
            fs.unlinkSync(file);
        } catch (unlinkErr) {
            // ignore any files which are missing (some are --agent type specific)
            if (unlinkErr.code !== 'ENOENT') {
                console.error(chalk('red'), unlinkErr);
            }
        }
    }

    cb();
}

app.
    version(cosi.app_version).
    option('-a, --all', 'Delete all COSI checks, graphs, and worksheets for this host').
    option('-c, --check [id]', 'Delete COSI check with [id] or all checks for this host').
    option('-g, --graph [id]', 'Delete COSI graph with [id] or all graphs for this host').
    option('-d, --dashboard [id]', 'Delete COSI dashboard with [id] or all dashboards for this host').
    option('-w, --worksheet [id]', 'Delete COSI worksheet with [id] or all worksheets for this host').
    option('-r, --ruleset [id]', 'Delete COSI ruleset with [id] or all rulesets').
    option('--notemplate', 'Keep template files, do not remove with registration and config files.').
    option('--configs', 'Remove the COSI configuration files. Combine with --all for a re-install.').
    option('-q, --quiet', 'Only error output').
    parse(process.argv);

// main

const items = [];

if (app.all || app.worksheet) {
    items.push.apply(items, findItems(cosi.reg_dir, 'worksheet', app.worksheet));
}

if (app.all || app.graph) {
    items.push.apply(items, findItems(cosi.reg_dir, 'graph', app.graph));
}

if (app.all || app.dashboard) {
    items.push.apply(items, findItems(cosi.reg_dir, 'dashboard', app.dashboard));
}

if (app.all || app.ruleset) {
    let files = [];

    try {
        files = fs.readdirSync(cosi.ruleset_dir);
    } catch (err) {
        console.error(chalk.yellow('WARN'), 'reading ruleset directory', err);
    }

    for (const file of files) {
        const cfgFile = null;
        const templateFile = null;
        let regFile = null;

        if (file.indexOf('-cosi.json') !== -1) {
            regFile = path.resolve(path.join(cosi.ruleset_dir, file));
            if (app.ruleset && file.indexOf(app.ruleset) === -1) {
                regFile = null;
            }
        }

        if (regFile !== null) {
            items.push({
                cfgFile,
                regFile,
                templateFile
            });
        }
    }
}

// always do checks last, remove everything dependent on the check(s) first
if (app.all || app.check) {
    items.push.apply(items, findItems(cosi.reg_dir, 'check', app.check));
}

if (items.length > 0) {
    const events = new Events();

    events.on('next', () => {
        const item = items.shift();

        if (typeof item === 'undefined') {
            events.emit('done');

            return;
        }

        deleteItem(item, app.notemplate, (err) => {
            if (err) {
                console.error(chalk.red('ERROR'), 'deleting item', item, err);
                process.exit(1);
            }
            events.emit('next');
        });
    });

    events.on('done', () => {
        if (app.configs) {
            removeConfigs(() => {
                console.log('reset complete');
            });
        } else {
            console.log('reset complete');
        }
    });

    events.emit('next');
} else if (app.configs) {
    removeConfigs(() => {
        console.log('configs removed');
    });
}

// END
<commit_msg>fix: remove pg plugin configs from nad_etc_dir<commit_after>#!/usr/bin/env node

// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/**
 * COSI reset
 *
 * remove COSI checks, graphs, and worksheets
 *
 * use api to delete in circonus
 * remove local registration file
 * remove local config file
 * remove local template file
 *
 */

'use strict';

const path = require('path');
const fs = require('fs');
const Events = require('events').EventEmitter;
const child = require('child_process');

const app = require('commander');
const chalk = require('chalk');

const cosi = require(path.resolve(path.join(__dirname, '..', 'lib', 'cosi')));
const api = require(path.resolve(cosi.lib_dir, 'api'));

/**
 * delete a specific item
 * @arg {Object} item definition
 * @arg {Boolean} keepTemplates flag
 * @arg {Function} cb callback
 * @returns {Undefined} nothing
 */
function deleteItem(item, keepTemplates, cb) {
    let cfg = {};

    try {
        cfg = require(item.regFile); // eslint-disable-line global-require
    } catch (err) {
        if (err.code === 'MODULE_NOT_FOUND') {
            console.log(chalk.yellow('WARN'), 'Registration not found', item.regFile);

            cb(null);

            return;
        }

        cb(err);

        return;
    }

    const itemURL = cfg._cid;
    const itemName = cfg.display_name || cfg.title || cfg.description || '';

    const cleaner = () => {
        try {
            console.log(`\tremoving ${item.regFile}`);
            fs.unlinkSync(item.regFile);
        } catch (err) {
            return err;
        }

        if (item.cfgFile !== null) {
            try {
                console.log(`\tremoving ${item.cfgFile}`);
                fs.unlinkSync(item.cfgFile);
            } catch (err) {
                return err;
            }
        }

        if (item.templateFile !== null) {
            if (keepTemplates) {
                console.log(`\tKEEPING ${item.templateFile}`);
            } else {
                try {
                    console.log(`\tremoving ${item.templateFile}`);
                    fs.unlinkSync(item.templateFile);
                } catch (err) {
                    // graph templates can have mulitple graphs, ignore any missing files
                    if (err.code !== 'ENOENT') {
                        return err;
                    }
                }
            }
        }

        return null;
    };

    console.log(chalk.bold('Checking'), `${itemName} ${itemURL}`);
    api.get(itemURL, null).
        then((res) => {
            if (res.code === 404 && (res.raw_body && res.raw_body.indexOf('requested object was not found') !== -1)) {
                console.log(`\t${itemURL}`, chalk.bold('not found'), '- cleaning up orphaned files.');
                cb(cleaner());

                return;
            }

            if (res.code < 200 || res.code > 299) {
                console.error(chalk.red('API RESULT CODE'), res.code, res.parsed_body);
                cb(res.parsed_body);

                return;
            }

            console.log(chalk.bold('\tDeleting'), `${itemName} ${itemURL}`);

            api.delete(itemURL).
                then((res2) => {
                    if (res2.code < 200 || res2.code > 299) {
                        console.error(chalk.red('API RESULT CODE'), res2.code, res2.parsed_body);
                        cb(res2.parsed_body);

                        return;
                    }

                    cb(cleaner());
                }).
                catch((err) => {
                    console.error(chalk.red('API ERROR'), err);
                    console.error('Attempt to run the command again. Contact support if the error persists.');
                    process.exit(1);
                });
        }).
        catch((err) => {
            if ({}.hasOwnProperty.call(err, 'raw_body') && err.raw_body.indexOf('requested object was not found') !== -1) {
                console.log(`\t${itemURL}`, chalk.bold('not found'), '- cleaning up orphaned files.');
                cb(cleaner());

                return;
            }
            console.error(chalk.red('ERROR'), 'An API error occurred', err);
            cb(err);
        });
}

/**
 * find asset registrations
 * @arg {String} dir to search
 * @arg {String} itemType to search for
 * @arg {String} itemId of specific asset
 * @returns {Array} list of items
 */
function findItems(dir, itemType, itemId) {
    let id = null;

    if (typeof itemId === 'string') {
        id = itemId;
        if (id.substring(0, itemType.length + 1) !== `${itemType}-`) {
            id = `${itemType}-${id}`;
        }
        id = `registration-${id}`;
    } else {
        id = `registration-${itemType}-`;
    }

    const re = new RegExp(`^${id}.*`);

    let files = null;

    try {
        files = fs.readdirSync(dir);
    } catch (err) {
        throw err;
    }

    const entries = [];

    for (const file of files) {
        if (itemType === 'check' && typeof itemId === 'undefined') {
            // don't remove the group check unless it is explicitly
            // specified on the command line. (--check=group).
            // there is no way to know how many other systems
            // still be depend on the check.
            if (file.includes('-group')) {
                continue;
            }
        }

        if (file.match(re)) {
            const regFile = path.resolve(path.join(dir, file));
            const cfgFile = regFile.replace('registration-', 'config-');
            let templateFile = regFile.replace('registration-', 'template-');

            if (file.match('registration-graph')) {
                const parts = file.split('-');

                if (parts) {
                    templateFile = path.resolve(path.join(dir, `template-graph-${parts[2]}.json`));
                }
            }

            entries.push({
                cfgFile,
                regFile,
                templateFile
            });
        }
    }

    console.log(`Found ${entries.length} ${itemType}s.`);

    return entries;
}

/**
 * remove configuration files
 * @arg {Function} cb callback
 * @returns {Undefined} nothing
 */
function removeConfigs(cb) {
    console.log('Removing COSI configuration files');

    if (cosi.agent_mode === 'reverse') {
        console.log(`\tre-installing default NAD config`);
        try {
            child.execSync(path.resolve(path.join(cosi.cosi_dir, 'bin', 'nadreverse_uninstall.sh')));
        } catch (err) {
            console.dir(err);
        }
    }

    const configFiles = [
        path.resolve(path.join(cosi.etc_dir, 'cosi.json')),
        path.resolve(path.join(cosi.etc_dir, 'circonus-nadreversesh')),
        path.resolve(path.join(cosi.etc_dir, 'statsd.json')),
        path.resolve(path.join(cosi.cosi_dir, '..', 'etc', 'circonus-nadpush.json')),
        path.resolve(path.join(cosi.nad_etc_dir, 'pg-conf.sh')),
        path.resolve(path.join(cosi.nad_etc_dir, 'pg-po-conf.sh')),
        path.resolve(path.join(cosi.reg_dir, 'setup-config.json')),
        path.resolve(path.join(cosi.reg_dir, 'setup-metrics.json'))
    ];

    for (let i = 0; i < configFiles.length; i++) {
        const file = configFiles[i];

        try {
            console.log(`\tremoving ${file}`);
            fs.unlinkSync(file);
        } catch (unlinkErr) {
            // ignore any files which are missing (some are --agent type specific)
            if (unlinkErr.code !== 'ENOENT') {
                console.error(chalk('red'), unlinkErr);
            }
        }
    }

    cb();
}

app.
    version(cosi.app_version).
    option('-a, --all', 'Delete all COSI checks, graphs, and worksheets for this host').
    option('-c, --check [id]', 'Delete COSI check with [id] or all checks for this host').
    option('-g, --graph [id]', 'Delete COSI graph with [id] or all graphs for this host').
    option('-d, --dashboard [id]', 'Delete COSI dashboard with [id] or all dashboards for this host').
    option('-w, --worksheet [id]', 'Delete COSI worksheet with [id] or all worksheets for this host').
    option('-r, --ruleset [id]', 'Delete COSI ruleset with [id] or all rulesets').
    option('--notemplate', 'Keep template files, do not remove with registration and config files.').
    option('--configs', 'Remove the COSI configuration files. Combine with --all for a re-install.').
    option('-q, --quiet', 'Only error output').
    parse(process.argv);

// main

const items = [];

if (app.all || app.worksheet) {
    items.push.apply(items, findItems(cosi.reg_dir, 'worksheet', app.worksheet));
}

if (app.all || app.graph) {
    items.push.apply(items, findItems(cosi.reg_dir, 'graph', app.graph));
}

if (app.all || app.dashboard) {
    items.push.apply(items, findItems(cosi.reg_dir, 'dashboard', app.dashboard));
}

if (app.all || app.ruleset) {
    let files = [];

    try {
        files = fs.readdirSync(cosi.ruleset_dir);
    } catch (err) {
        console.error(chalk.yellow('WARN'), 'reading ruleset directory', err);
    }

    for (const file of files) {
        const cfgFile = null;
        const templateFile = null;
        let regFile = null;

        if (file.indexOf('-cosi.json') !== -1) {
            regFile = path.resolve(path.join(cosi.ruleset_dir, file));
            if (app.ruleset && file.indexOf(app.ruleset) === -1) {
                regFile = null;
            }
        }

        if (regFile !== null) {
            items.push({
                cfgFile,
                regFile,
                templateFile
            });
        }
    }
}

// always do checks last, remove everything dependent on the check(s) first
if (app.all || app.check) {
    items.push.apply(items, findItems(cosi.reg_dir, 'check', app.check));
}

if (items.length > 0) {
    const events = new Events();

    events.on('next', () => {
        const item = items.shift();

        if (typeof item === 'undefined') {
            events.emit('done');

            return;
        }

        deleteItem(item, app.notemplate, (err) => {
            if (err) {
                console.error(chalk.red('ERROR'), 'deleting item', item, err);
                process.exit(1);
            }
            events.emit('next');
        });
    });

    events.on('done', () => {
        if (app.configs) {
            removeConfigs(() => {
                console.log('reset complete');
            });
        } else {
            console.log('reset complete');
        }
    });

    events.emit('next');
} else if (app.configs) {
    removeConfigs(() => {
        console.log('configs removed');
    });
}

// END
<|endoftext|>


<commit_before>// js/db.js

const Datastore = require('nedb');
const Logger = require('./log')
const logger = new Logger('js/db.js');

class Database {
    constructor(directory) {
        this.directory = directory;

        this.dbApps = new Datastore({filename: this.directory + 'apps.db'});
        this.dbApps.ensureIndex({ fieldName: 'name', unique: true }, ()=>{})
        ;
        this.dbApps.loadDatabase((err)=> {
            if (err) {
                logger.log(err.message);
            }
        })
    }

    addApp(name, url, html, callback /* args: (err, newDoc*/) {
        this.dbApps.insert({'name': name, 'url': url, 'html':html}, callback);
        this.dbApps.persistence.compactDatafile();
    }

    loadApps(callback /*args: (err, docs) */) {
        this.dbApps.find({}, callback);
    }

    getApp(name, callback /* args: (err, docs)*/) {
        this.dbApps.find({'name': name}, callback);
        this.dbApps.persistence.compactDatafile();
    }

    editApp(name, newName, newUrl, newHtml, callback /* args: (err, numAffected, affectedDocuments, upsert))*/) {
        this.dbApps.update({ 'name': name }, { $set: { 'name':newName , 'url': newUrl, 'html':newHtml } }, {}, callback);
        this.dbApps.persistence.compactDatafile();
    }

    deleteApp(name, callback /* args: (err, numRemoved)*/) {
        this.dbApps.remove({'name':name}, callback);
        this.dbApps.persistence.compactDatafile();
    }
}

module.exports = Database;<commit_msg>changed api to write type of added app in addApp method<commit_after>// js/db.js

const Datastore = require('nedb');
const Logger = require('./log')
const logger = new Logger();
//const logger = require('electron').remote.getGlobal('logger');

class Database {
    constructor(directory) {
        this.directory = directory;

        this.dbApps = new Datastore({filename: this.directory + 'apps.db'});
        this.dbApps.ensureIndex({ fieldName: 'name', unique: true }, ()=>{});
        this.dbApps.loadDatabase((err)=> {
            if (err) {
                logger.log(err.message);
            }
        })
    }

    addApp(name, url, html, typ,  callback /* args: (err, newDoc*/) {
        this.dbApps.insert({'name': name, 'url': url, 'html':html, 'type':typ}, callback);
        this.dbApps.persistence.compactDatafile();
    }

    loadApps(callback /*args: (err, docs) */) {
        this.dbApps.find({}, callback);
    }

    getApp(name, callback /* args: (err, docs)*/) {
        this.dbApps.find({'name': name}, callback);
        this.dbApps.persistence.compactDatafile();
    }

    editApp(name, newName, newUrl, newHtml, callback /* args: (err, numAffected, affectedDocuments, upsert))*/) {
        this.dbApps.update({ 'name': name }, { $set: { 'name':newName , 'url': newUrl, 'html':newHtml } }, {}, callback);
        this.dbApps.persistence.compactDatafile();
    }

    deleteApp(name, callback /* args: (err, numRemoved)*/) {
        this.dbApps.remove({'name':name}, callback);
        this.dbApps.persistence.compactDatafile();
    }
}

module.exports = Database;<|endoftext|>


<commit_before>var Toolbar = {
	event: function(e) {
		/*
		console.log(e);
		//chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
		//console.log(tabs[0])
		//  chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
		//    console.log(response);
		//  });
		//});
		//  chrome.tabs.getSelected(null, function(tab) {
		chrome.runtime.sendMessage({greeting: "stumble"}, function(response) {
			console.log('moop',response);
		});
		//  });
		*/
	},

	handleUrl: function(url) {
		Toolbar.url = url;
		document.querySelector("#like")   .removeClass("enabled");
		document.querySelector("#dislike").removeClass("enabled");
		document.querySelector("#stumble").removeClass("enabled");
		if (url.userRating) {
			if (url.userRating.type >= 1)
				document.querySelector("#like").addClass("enabled");
			if (url.userRating.type <= -1)
				document.querySelector("#dislike").addClass("enabled");
		}

		var message = "Be the first to like this!"
		if (url.likes) {
			message = "Liked by " + String(url.likes).numberFormat() + " people";
		}
		document.querySelector("#inline-info-body").innerHTML = message;

		document.querySelector("#info").removeClass("on");
		if (url.urlid) {
			document.querySelector("#info").addClass("on");
		}

		if (url.hasOwnProperty('sponsored')) {
			document.querySelector("#sponsored").changeClass("hidden", !url.sponsored);
		}
	},

	handleConfig: function(config) {
		if (config.theme && config.theme != Toolbar.config.theme) {
			var classes = document.querySelector("#toolbar").classList;
			for (var i = 0; i < classes.length; i++)
				if (classes[i].indexOf('theme-') === 0)
					document.querySelector("#toolbar").removeClass(classes[i]);
			document.querySelector("#toolbar").addClass('theme-' + config.theme);
			document.querySelectorAll(".action-theme").forEach(function(elem) {
				elem.removeClass('enabled');
			});
			document.querySelector("#theme-" + config.theme).addClass('enabled');
		}
		if (config.mode && config.mode != Toolbar.config.mode) {
			//document.querySelector(".toolbar-mode-selection").addClass("hidden");
			//document.querySelector(".toolbar-mode").removeClass("hidden");
			document.querySelector("#mode").innerText = config.modes[config.mode].name;
		}

		if (config.hasOwnProperty('numShares')) {
			document.querySelector("#inbox .badge").innerText = config.numShares ? parseInt(config.numShares) : '';
			document.querySelector("#inbox").changeClass('enabled', parseInt(config.numShares));
		}

		if (config.hasOwnProperty('authed')) {
			document.querySelector("#stumble").changeClass('hidden', !config.authed);
			document.querySelector("#signin") .changeClass('hidden', config.authed);
			if (!config.authed)
				Toolbar.handleMiniMode();
		}

		for (var key in config) {
			Toolbar.config[key] = config[key];
		}

		Toolbar.handleRedraw();
	},

	handleResponse: function(r) {
		console.log('Toolbar.handleResponse', r);
		if (r && r.url) {
			Toolbar.handleUrl(r.url);
		}
		if (r && r.config) {
			Toolbar.handleConfig(r.config);
		}
		if (!r || r.from != 'bar')
			Toolbar.handleRedraw();
		return true;
	},
	dispatch: function(a, data) {
		return new Promise(function (resolve, reject) {
			chrome.runtime.sendMessage({
				action: a,
				url: Toolbar.url || {},
				data: data || {}
			}, resolve);
		});
	},
	handleEvent: function(e) {
		if (Toolbar.mouse.state == 'up') {
			Toolbar.mouse.state = null;
			return;
		}
		var elem   = e.target;
		while (elem && elem.getAttribute && !elem.getAttribute('action')) {
			elem = elem.parentNode;
		}
		if (!elem || !elem.getAttribute)
			return;
		var action = elem.getAttribute('action');
		var value  = elem.getAttribute('value');

		Toolbar.handleImmediateAction(action, value);
		Toolbar.dispatch(action, {value: value})
			.then(Toolbar.handleResponse);
		Toolbar.handleRedraw();
	},

	handleImmediateAction: function(action, value) {
		if (action == "su") {
			chrome.tabs.create({ url: Toolbar.config.suPages[value].form(Toolbar.config) });
		}
		if (action == 'stumble' || action == 'mode') {
			document.querySelector(".action-stumble").toggleClass("enabled");
			document.querySelector(".toolbar-container").removeClass("mode-expanded");
		}
		if (action == 'expand' && value == 'inline-info') {
			document.querySelector(".toolbar-container").toggleClass("inline-info-expanded");
		}
		if (action == 'expand' && value == 'inbox') {
			document.querySelector(".toolbar-container").toggleClass("inbox-expanded");
		}
		if (action == 'expand' && value == 'mode') {
			document.querySelector(".toolbar-container").toggleClass("mode-expanded");
		}
		if (action == 'expand' && value == 'social') {
			//document.querySelector(".toolbar-social-container .toolbar-expand-icon").toggleClass("enabled");
			//document.querySelector(".action-inbox").toggleClass("enabled");
		}
		if (action == 'settings') {
			elem.toggleClass("enabled");
			document.querySelector(".toolbar-settings-container").toggleClass("hidden");
		}
		if (action == 'hide') {
		}
	},
	mouse: {},
	config: {},
	state: {
		lastMouse:     Date.now(),
		canMiniMode:   false,
		inMiniMode:    false,
	},
	handleMouseDown: function(e) {
		Toolbar.mouse = { state: 'down', pos: { x: e.screenX, y: e.screenY } };
		window.top.postMessage({ type: "down", message: { screen: { x: e.screenX, y: e.screenY }, client: { x: e.clientX, y: e.clientY } } }, "*");
	},
	handleMouseMove: function(e) {
		if (!e.button && !e.buttons)
			Toolbar.mouse.state = 'up';
		if (Toolbar.mouse.state == 'down' && Math.max(Math.abs(Toolbar.mouse.pos.x - e.screenX), Math.abs(Toolbar.mouse.pos.y - e.screenY)) >= 8)
			Toolbar.mouse.state = 'drag';
		if (Toolbar.mouse.state == 'drag')
			window.top.postMessage({ type: "drag", message: { screen: { x: e.screenX, y: e.screenY }, client: { x: e.clientX, y: e.clientY } } }, "*");
		Toolbar.state.lastMouse = Date.now();
		Toolbar.state.canMiniMode = false;
		if (Toolbar.state.inMiniMode)
			Toolbar.handleNormalMode(e);
	},
	handleMouseUp: function(e) {
		if (Toolbar.mouse.state == 'drag')
			Toolbar.mouse.state = 'up';
		else
			Toolbar.mouse.state = null;
		e.stopPropagation();
		window.top.postMessage({ type: "up", message: { screen: { x: e.screenX, y: e.screenY }, client: { x: e.clientX, y: e.clientY } } }, "*");
	},
	tryMiniMode: function(e) {
		if (Toolbar.state.canMiniMode && !Toolbar.state.inMiniMode && Toolbar.state.lastMouse && Date.now() - Toolbar.state.lastMouse >= (Toolbar.config.miniModeTimeout || 10)) {
			Toolbar.handleMiniMode(e);
		}
	},
	handleMiniMode: function(e) {
		Toolbar.state.inMiniMode = true || !Toolbar.config.authed;
		document.querySelector("#toolbar").addClass("mini-mode");
		Toolbar.handleRedraw();
	},
	handleNormalMode: function(e) {
		if (!Toolbar.config.authed)
			return false;
		Toolbar.state.inMiniMode = false;
		document.querySelector("#toolbar").removeClass("mini-mode");
		Toolbar.handleRedraw();
	},
	handleRedraw: function() {
		if (Toolbar.config.rpos) {
			document.querySelector('#toolbar').changeClass('top-handed',   Toolbar.config.rpos.vside == 'top');
			document.querySelector('#toolbar').changeClass('right-handed', Toolbar.config.rpos.hside == 'right');
		}
		window.top.postMessage({ type: "redraw", message: { toolbar: {
			w: Toolbar.state.w = document.querySelector(".toolbar-section-container").offsetWidth,
			h: Toolbar.state.h = document.querySelector(".toolbar-section-container").offsetHeight,
			rpos: Toolbar.config.rpos,
			hidden: Toolbar.config.hidden,
		} } }, "*");
	},
	handleIframeEvent: function(e) {
		if (e.data.action == 'mouse') {
			var data = e.data.data;
			Toolbar.state.canMiniMode = !data || !data.iframe || !( // We can do mini mode if the mouse isn't hovering over the frame
				   data.iframe.x < data.mouse.x && data.iframe.x + Toolbar.state.w > data.mouse.x
				&& data.iframe.y < data.mouse.y && data.iframe.y + Toolbar.state.h > data.mouse.y
			);
			return;
		}
		Toolbar.dispatch(e.data.action, e.data.data).then(Toolbar.handleResponse);
	},
	init: function() {
		// Event and message handling
		document.getElementById("toolbar").addEventListener("click", Toolbar.handleEvent);
		chrome.runtime.onMessage.addListener(Toolbar.handleResponse);
		window.addEventListener("message", Toolbar.handleIframeEvent, false);

		// Toolbar initialization
		Toolbar.dispatch('init')
			   .then(Toolbar.handleResponse);
		Toolbar.dispatch('urlChange')
			   .then(Toolbar.handleResponse);
		window.setInterval(Toolbar.tryMiniMode, 1000);

		// Drag-n-drop logic
		document.addEventListener("mousedown", Toolbar.handleMouseDown);
		document.addEventListener("mousemove", Toolbar.handleMouseMove);
		document.addEventListener("mouseup",   Toolbar.handleMouseUp);

		// Redraw
		//Toolbar.handleRedraw();
		//Toolbar._events.forEach(function(entry) {
		//	document.getElementById(entry.id).addEventListener(entry.ev, Toolbar[entry.cb])
		//});
	},
	_events: [{ ev: 'click', id: 'stumble', cb: 'stumble' }]
}
Toolbar.init();

<commit_msg>Fix settings<commit_after>var Toolbar = {
	event: function(e) {
		/*
		console.log(e);
		//chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
		//console.log(tabs[0])
		//  chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
		//    console.log(response);
		//  });
		//});
		//  chrome.tabs.getSelected(null, function(tab) {
		chrome.runtime.sendMessage({greeting: "stumble"}, function(response) {
			console.log('moop',response);
		});
		//  });
		*/
	},

	handleUrl: function(url) {
		Toolbar.url = url;
		document.querySelector("#like")   .removeClass("enabled");
		document.querySelector("#dislike").removeClass("enabled");
		document.querySelector("#stumble").removeClass("enabled");
		if (url.userRating) {
			if (url.userRating.type >= 1)
				document.querySelector("#like").addClass("enabled");
			if (url.userRating.type <= -1)
				document.querySelector("#dislike").addClass("enabled");
		}

		var message = "Be the first to like this!"
		if (url.likes) {
			message = "Liked by " + String(url.likes).numberFormat() + " people";
		}
		document.querySelector("#inline-info-body").innerHTML = message;

		document.querySelector("#info").removeClass("on");
		if (url.urlid) {
			document.querySelector("#info").addClass("on");
		}

		if (url.hasOwnProperty('sponsored')) {
			document.querySelector("#sponsored").changeClass("hidden", !url.sponsored);
		}
	},

	handleConfig: function(config) {
		if (config.theme && config.theme != Toolbar.config.theme) {
			var classes = document.querySelector("#toolbar").classList;
			for (var i = 0; i < classes.length; i++)
				if (classes[i].indexOf('theme-') === 0)
					document.querySelector("#toolbar").removeClass(classes[i]);
			document.querySelector("#toolbar").addClass('theme-' + config.theme);
			document.querySelectorAll(".action-theme").forEach(function(elem) {
				elem.removeClass('enabled');
			});
			document.querySelector("#theme-" + config.theme).addClass('enabled');
		}
		if (config.mode && config.mode != Toolbar.config.mode) {
			//document.querySelector(".toolbar-mode-selection").addClass("hidden");
			//document.querySelector(".toolbar-mode").removeClass("hidden");
			document.querySelector("#mode").innerText = config.modes[config.mode].name;
		}

		if (config.hasOwnProperty('numShares')) {
			document.querySelector("#inbox .badge").innerText = config.numShares ? parseInt(config.numShares) : '';
			document.querySelector("#inbox").changeClass('enabled', parseInt(config.numShares));
		}

		if (config.hasOwnProperty('authed')) {
			document.querySelector("#stumble").changeClass('hidden', !config.authed);
			document.querySelector("#signin") .changeClass('hidden', config.authed);
			if (!config.authed)
				Toolbar.handleMiniMode();
		}

		for (var key in config) {
			Toolbar.config[key] = config[key];
		}

		Toolbar.handleRedraw();
	},

	handleResponse: function(r) {
		console.log('Toolbar.handleResponse', r);
		if (r && r.url) {
			Toolbar.handleUrl(r.url);
		}
		if (r && r.config) {
			Toolbar.handleConfig(r.config);
		}
		if (!r || r.from != 'bar')
			Toolbar.handleRedraw();
		return true;
	},
	dispatch: function(a, data) {
		return new Promise(function (resolve, reject) {
			chrome.runtime.sendMessage({
				action: a,
				url: Toolbar.url || {},
				data: data || {}
			}, resolve);
		});
	},
	handleEvent: function(e) {
		if (Toolbar.mouse.state == 'up') {
			Toolbar.mouse.state = null;
			return;
		}
		var elem   = e.target;
		while (elem && elem.getAttribute && !elem.getAttribute('action')) {
			elem = elem.parentNode;
		}
		if (!elem || !elem.getAttribute)
			return;
		var action = elem.getAttribute('action');
		var value  = elem.getAttribute('value');

		Toolbar.handleImmediateAction(action, value, elem);
		Toolbar.dispatch(action, {value: value})
			.then(Toolbar.handleResponse);
		Toolbar.handleRedraw();
	},

	handleImmediateAction: function(action, value, elem) {
		if (action == "su") {
			chrome.tabs.create({ url: Toolbar.config.suPages[value].form(Toolbar.config) });
		}
		if (action == 'stumble' || action == 'mode') {
			document.querySelector(".action-stumble").toggleClass("enabled");
			document.querySelector(".toolbar-container").removeClass("mode-expanded");
		}
		if (action == 'expand' && value == 'inline-info') {
			document.querySelector(".toolbar-container").toggleClass("inline-info-expanded");
		}
		if (action == 'expand' && value == 'inbox') {
			document.querySelector(".toolbar-container").toggleClass("inbox-expanded");
		}
		if (action == 'expand' && value == 'mode') {
			document.querySelector(".toolbar-container").toggleClass("mode-expanded");
		}
		if (action == 'expand' && value == 'social') {
			//document.querySelector(".toolbar-social-container .toolbar-expand-icon").toggleClass("enabled");
			//document.querySelector(".action-inbox").toggleClass("enabled");
		}
		if (action == 'settings') {
			elem.toggleClass("enabled");
			document.querySelector(".toolbar-settings-container").toggleClass("hidden");
		}
		if (action == 'hide') {
		}
	},
	mouse: {},
	config: {},
	state: {
		lastMouse:     Date.now(),
		canMiniMode:   false,
		inMiniMode:    false,
	},
	handleMouseDown: function(e) {
		Toolbar.mouse = { state: 'down', pos: { x: e.screenX, y: e.screenY } };
		window.top.postMessage({ type: "down", message: { screen: { x: e.screenX, y: e.screenY }, client: { x: e.clientX, y: e.clientY } } }, "*");
	},
	handleMouseMove: function(e) {
		if (!e.button && !e.buttons)
			Toolbar.mouse.state = 'up';
		if (Toolbar.mouse.state == 'down' && Math.max(Math.abs(Toolbar.mouse.pos.x - e.screenX), Math.abs(Toolbar.mouse.pos.y - e.screenY)) >= 8)
			Toolbar.mouse.state = 'drag';
		if (Toolbar.mouse.state == 'drag')
			window.top.postMessage({ type: "drag", message: { screen: { x: e.screenX, y: e.screenY }, client: { x: e.clientX, y: e.clientY } } }, "*");
		Toolbar.state.lastMouse = Date.now();
		Toolbar.state.canMiniMode = false;
		if (Toolbar.state.inMiniMode)
			Toolbar.handleNormalMode(e);
	},
	handleMouseUp: function(e) {
		if (Toolbar.mouse.state == 'drag')
			Toolbar.mouse.state = 'up';
		else
			Toolbar.mouse.state = null;
		e.stopPropagation();
		window.top.postMessage({ type: "up", message: { screen: { x: e.screenX, y: e.screenY }, client: { x: e.clientX, y: e.clientY } } }, "*");
	},
	tryMiniMode: function(e) {
		if (Toolbar.state.canMiniMode && !Toolbar.state.inMiniMode && Toolbar.state.lastMouse && Date.now() - Toolbar.state.lastMouse >= (Toolbar.config.miniModeTimeout || 10)) {
			Toolbar.handleMiniMode(e);
		}
	},
	handleMiniMode: function(e) {
		Toolbar.state.inMiniMode = true || !Toolbar.config.authed;
		document.querySelector("#toolbar").addClass("mini-mode");
		Toolbar.handleRedraw();
	},
	handleNormalMode: function(e) {
		if (!Toolbar.config.authed)
			return false;
		Toolbar.state.inMiniMode = false;
		document.querySelector("#toolbar").removeClass("mini-mode");
		Toolbar.handleRedraw();
	},
	handleRedraw: function() {
		if (Toolbar.config.rpos) {
			document.querySelector('#toolbar').changeClass('top-handed',   Toolbar.config.rpos.vside == 'top');
			document.querySelector('#toolbar').changeClass('right-handed', Toolbar.config.rpos.hside == 'right');
		}
		window.top.postMessage({ type: "redraw", message: { toolbar: {
			w: Toolbar.state.w = document.querySelector(".toolbar-section-container").offsetWidth,
			h: Toolbar.state.h = document.querySelector(".toolbar-section-container").offsetHeight,
			rpos: Toolbar.config.rpos,
			hidden: Toolbar.config.hidden,
		} } }, "*");
	},
	handleIframeEvent: function(e) {
		if (e.data.action == 'mouse') {
			var data = e.data.data;
			Toolbar.state.canMiniMode = !data || !data.iframe || !( // We can do mini mode if the mouse isn't hovering over the frame
				   data.iframe.x < data.mouse.x && data.iframe.x + Toolbar.state.w > data.mouse.x
				&& data.iframe.y < data.mouse.y && data.iframe.y + Toolbar.state.h > data.mouse.y
			);
			return;
		}
		Toolbar.dispatch(e.data.action, e.data.data).then(Toolbar.handleResponse);
	},
	init: function() {
		// Event and message handling
		document.getElementById("toolbar").addEventListener("click", Toolbar.handleEvent);
		chrome.runtime.onMessage.addListener(Toolbar.handleResponse);
		window.addEventListener("message", Toolbar.handleIframeEvent, false);

		// Toolbar initialization
		Toolbar.dispatch('init')
			   .then(Toolbar.handleResponse);
		Toolbar.dispatch('urlChange')
			   .then(Toolbar.handleResponse);
		window.setInterval(Toolbar.tryMiniMode, 1000);

		// Drag-n-drop logic
		document.addEventListener("mousedown", Toolbar.handleMouseDown);
		document.addEventListener("mousemove", Toolbar.handleMouseMove);
		document.addEventListener("mouseup",   Toolbar.handleMouseUp);

		// Redraw
		//Toolbar.handleRedraw();
		//Toolbar._events.forEach(function(entry) {
		//	document.getElementById(entry.id).addEventListener(entry.ev, Toolbar[entry.cb])
		//});
	},
	_events: [{ ev: 'click', id: 'stumble', cb: 'stumble' }]
}
Toolbar.init();

<|endoftext|>


<commit_before>var proxyMiddleware = require('http-proxy-middleware');

module.exports = {
  port: 3000,
  open: false,
  files: ["./dist/**/*.{html,css,js,less}"],
  server: {
    baseDir: './dist',
    routes: {
      '/output': '../output'
    },
    middleware: {
      1: proxyMiddleware('/api', {
        target: 'http://localhost:5566',
      changeOrigin: true   // for vhosted sites, changes host header to match to target's host
      })
    }
  }
};
<commit_msg>chore: Also forward /go endpoint to API server<commit_after>var proxyMiddleware = require('http-proxy-middleware');

module.exports = {
  port: 3000,
  open: false,
  files: ["./dist/**/*.{html,css,js,less}"],
  server: {
    baseDir: './dist',
    routes: {
      '/output': '../output'
    },
    middleware: {
      1: proxyMiddleware('/api', {
        target: 'http://localhost:5566',
      changeOrigin: true   // for vhosted sites, changes host header to match to target's host
      }),
      2: proxyMiddleware('/go', {
        target: 'http://localhost:5566',
      changeOrigin: true   // for vhosted sites, changes host header to match to target's host
      }),

    }
  }
};
<|endoftext|>


<commit_before>/** @jsx React.DOM */

// Treeview component
var Treeview = React.createClass({
    name: 'treeview',
    mixins: [getCommonMixin],

    // attribute definitions
    getAttributes: function() {
        var attributes = [
            { name:'boxClass', type:'string', required:false, defaultValue:'', note:'container CSS class' },
            { name:'type', type:'string', required:false, defaultValue:'', note:'treeview type' },
            { name:'expandLevel', type:'number', required:false, defaultValue:3, note:'levels to expand' },
            { name:'treedata', type:'array', required:false, defaultValue:[], note:'input treeview data' },
            // internal, don't pass in
            { name:'treedataObject', type:'object', required:false, defaultValue:[], note:'treeview data object' },
            { name:'treedataCol', type:'array', required:false, defaultValue:[], note:'treeview data in hash array' }
        ];
        return attributes;
    },

    componentWillMount: function() {
        this.state.treedataObject = this.prepareTreedata(this.state.treedata);
        this.state.nodes = this.getNodesFromTreeItem(this.state.treedataObject);
    },

    // add unique id to treedata
    prepareTreedata: function(treedata) {
        // convert object into array
        var startLevel = 1;
        this.state.treedataCol = {};
        if (typeof treedata === 'object') {
            if (treedata.constructor.name === 'Array') {
                startLevel = 0;
                treedata = { children:treedata };
            }
        }
        this.processTreedataItem(treedata, startLevel);
        return treedata;
    },

    processTreedataItem: function(item, level) {
        item.level = level;
        item.uid = this.generateUid();
        item.isSelected = false;
        item.isExpanded = (level < this.state.expandLevel) || false;
        item.hasChildren = (item.children && item.children.length > 0) || false;
        if (level > 0) {
            this.state.treedataCol[item.uid] = item;
        }
        if (item.children) {
            for (var i = 0; i < item.children.length; i++) {
                var childItem = item.children[i];
                this.processTreedataItem(childItem, level + 1);
            }
        }
    },

    /*
    Getting nodes from treedata item
    Example input treedata:
    [
        {
            "name": "Top Level",
            "children": [
                {
                    "name": "Level 2: A",
                    "children": [
                        {
                            "name": "Son of A"
                        },
                        {
                            "name": "Daughter of A"
                        }
                    ]
                },
                {
                    "name": "Level 2: B"
                },
                {
                    "name": "Level 2: C",
                    "children": [
                        {
                            "name": "Son of C"
                        }
                    ]
                }
            ]
        }
    ]
    output nodes:
    [
        { display:'Top Level', level:1 },
        { display:'Level 2: A', level:2 },
        { display:'Son of A', level:3 },
        { display:'Daughter of A', level:3 },
        { display:'Level 2: B', level:2 },
        { display:'Level 2: C', level:2 },
        { display:'Son of C', level:3 }
    ]
    */
    getNodesFromTreeItem: function(item) {
        var nodes = [];
        var currentNode = { isExpanded:true };
        var display = item.name || item.text || 'NA';
        // skip level 0, it is artificial object for array input
        if (item.level > 0) {
            currentNode = {
                uid: item.uid,
                item: item,
                display: display,
                level: item.level,
                hasChildren: item.hasChildren,
                isExpanded: item.isExpanded,
                isSelected: item.isSelected,
                boxClass: item.boxClass
            };
            nodes.push(currentNode);
        }
        if (item.children && currentNode.isExpanded) {
            var childNodes = [];
            for (var i = 0; i < item.children.length; i++) {
                var childItem = item.children[i];
                var childNodes = this.getNodesFromTreeItem(childItem);
                nodes = nodes.concat(childNodes);
            }
        }
        return nodes;
    },

    onClick: function(event) {
        var target = $(event.target);
        // get cellId from cell icon  or from cell container
        var cellId = target.attr('data-uid');
        if (target.hasClass('treeview-cell-expand-container')) {
            cellId = target.find('i').attr('data-uid');
        }
        if (!cellId) {
            return;
        }
        // cellId format: "<nodeKey>-<cellType>"
        // cellId example: "36c2b148338c8d7a7f99ef6b881e281f-expand"
        var parts = cellId.split('-');
        var nodeKey = parts[0];
        var cellType = parts[1];
        var treedataItem = this.state.treedataCol[nodeKey];
        // change this.state.treedata according to type of icon clicked
        switch (cellType) {
            case 'expand':
                treedataItem.isExpanded = !treedataItem.isExpanded;
                break;
            case 'icon':
                break;
            case 'select':
                treedataItem.isSelected = !treedataItem.isSelected;
                break;
        }
        this.state.nodes = this.getNodesFromTreeItem(this.state.treedataObject);
        // update display
        this.forceUpdate();
    },

    // set data for display
    setData: function(treedata) {
        this.state.treedata = treedata;
        this.state.treedataObject = this.prepareTreedata(this.state.treedata);
        this.state.nodes = this.getNodesFromTreeItem(this.state.treedataObject);
        this.forceUpdate();
    },

    // get selected items
    getSelected: function() {
        var resultItems = [];
        var domNode = this.getDOMNode();
        var selectedIcons = $(domNode).find('.treeview-cell-select-container .fa-check-square-o');
        for (var i = 0; i < selectedIcons.length; i ++) {
            var uid = $(selectedIcons[i]).attr('data-uid').split('-')[0];
            var item = this.state.treedataCol[uid];
            resultItems.push(item);
        }
        return resultItems;
    },

    render: function() {
        var treenodes = [];
        for (var i = 0; i < this.state.nodes.length; i++) {
            var node = this.state.nodes[i];
            // need to include node properties tied with UI change, for example: isSelected
            var nodeKey = 'treenode-' + node.uid;
            nodeKey += '-' + (node.isSelected ? 'S' : 'NS');
            nodeKey += '-' + (node.isExpanded ? 'E' : 'NE');
            treenodes.push(<TreeviewNode data={ node } key={ nodeKey } />);
        }
        return (

                    { treenodes }

        );
    }
});

// TreeviewNode component
var TreeviewNode = React.createClass({
    name: 'treeview-node',
    mixins: [getCommonMixin],

    // attribute definitions
    getAttributes: function() {
        var attributes = [
            { name:'boxClass', type:'string', required:false, defaultValue:'', note:'container CSS class' },
            { name:'uid', type:'string', required:false, defaultValue:'', note:'unique id for node' },
            { name:'level', type:'number', required:false, defaultValue:1, note:'node level' },
            { name:'display', type:'string', required:false, defaultValue:'', note:'text display' },
            { name:'hasChildren', type:'boolean', required:false, defaultValue:false, note:'has children flag' },
            { name:'isExpanded', type:'boolean', required:false, defaultValue:false, note:'is expanded flag' },
            { name:'isSelected', type:'boolean', required:false, defaultValue:false, note:'is selected flag' },
            { name:'item', type:'object', required:false, defaultValue:'', note:'original data item' }
        ];
        return attributes;
    },

    /*
    onClick: function(event) {
        var element = event.currentTarget;
        var uid = $(element).attr('data-id');
        console.log('clicked on node - uid:', uid, ' data:', this.state.item);
    },
    */

    render: function() {
        var items = [];
        var cells = [];
        var level = this.state.level;

        // add branch cells
        for (var i = 1; i < level; i++) {
            var cellData = { text:i, type:'space' };
            var cellKey = 'treeview-text-cell-space-' + i;
            cells.push(
                <TreeviewCell data={ cellData } key={ cellKey } />
            );
        }

        // add expand cell if there are children
        var expandCellData = {
            uid: this.state.uid + '-expand',
            type: 'expand',
            iconClass: this.state.isExpanded ? 'fa fa-caret-down' : 'fa fa-caret-right'
        };
        if (!this.state.hasChildren) {
            expandCellData.iconClass = 'fa';
        }
        cells.push(<TreeviewCell data={ expandCellData } key='treeview-expand-cell' />);

        // add select cell
        var selectCellData = {
            uid: this.state.uid + '-select',
            type: 'select',
            iconClass: this.state.isSelected ? 'fa fa-check-square-o' : 'fa fa-square-o'
        };
        var selectCellKey = 'treeview-select-cell';
        cells.push(<TreeviewCell data={ selectCellData } key={ selectCellKey } />);

        // only add icon cell when iconClass is present
        if (this.state.item.iconClass) {
            var iconCellData = {
                uid: this.state.uid + '-icon',
                type: 'icon',
                iconClass: this.state.item.iconClass
            };
            cells.push(<TreeviewCell data={ iconCellData } key='treeview-icon-cell' />);
        }

        // add textbox
        var textboxData = {
            uid: this.state.uid + '-textbox',
            type: 'text',
            text: this.state.display
        };
        cells.push(<TreeviewTextBox data={ textboxData } key='treeview-textbox' />);

        return (

                { cells }

        );
    }
});

// TreeviewCell component
var TreeviewCell = React.createClass({
    name: 'treeview-cell',
    mixins: [getCommonMixin],

    // attribute definitions
    getAttributes: function() {
        var attributes = [
            { name:'boxClass', type:'string', required:false, defaultValue:'', note:'container CSS class' },
            { name:'iconClass', type:'string', required:false, defaultValue:'', note:'icon CSS class' },
            { name:'uid', type:'string', required:false, defaultValue:'', note:'unique id for cell' },
            { name:'type', type:'string', required:false, defaultValue:'', note:'treeview type' }
        ];
        return attributes;
    },

    render: function() {
        // set iconClass
        var displayText = '';
        var iconClass = this.state.iconClass || '';
        var iconContainerClass = 'treeview-cell-' + this.state.type + '-container';
        switch (this.state.type) {
            case 'text':
                displayText = this.state.text;
                break;
            case 'expand':
                iconClass = iconClass || 'fa fa-plus-square-o';
                break;
            case 'select':
                iconClass = iconClass || 'fa fa-check-square-o';
                break;
            case 'icon':
                iconClass = iconClass || '';
                break;
        }
        return (

                    { displayText }

        );
    }
});

// Treeview textbox component
var TreeviewTextBox = React.createClass({
    name: 'treeview-textbox',
    mixins: [getCommonMixin],

    // attribute definitions
    getAttributes: function() {
        var attributes = [
            { name:'boxClass', type:'string', required:false, defaultValue:'', note:'container CSS class' },
            { name:'uid', type:'string', required:false, defaultValue:'', note:'unique id for cell' },
            { name:'text', type:'string', required:false, defaultValue:'', note:'display text' },
            { name:'type', type:'string', required:false, defaultValue:'', note:'treeview type' }
        ];
        return attributes;
    },

    render: function() {
        // set content
        var displayText = this.state.text;
        return (

        );
    }
});
<commit_msg>change node state to three levels: all, partial, none<commit_after>/** @jsx React.DOM */

// Treeview component
var Treeview = React.createClass({
    name: 'treeview',
    mixins: [getCommonMixin],

    // attribute definitions
    getAttributes: function() {
        var attributes = [
            { name:'boxClass', type:'string', required:false, defaultValue:'', note:'container CSS class' },
            { name:'type', type:'string', required:false, defaultValue:'', note:'treeview type' },
            { name:'expandLevel', type:'number', required:false, defaultValue:3, note:'levels to expand' },
            { name:'treedata', type:'array', required:false, defaultValue:[], note:'input treeview data' },
            // internal, don't pass in
            { name:'treedataObject', type:'object', required:false, defaultValue:[], note:'treeview data object' },
            { name:'treedataCol', type:'array', required:false, defaultValue:[], note:'treeview data in hash array' }
        ];
        return attributes;
    },

    componentWillMount: function() {
        this.state.treedataObject = this.prepareTreedata(this.state.treedata);
        this.state.nodes = this.getNodesFromTreeItem(this.state.treedataObject);
    },

    // add unique id to treedata
    prepareTreedata: function(treedata) {
        // convert object into array
        var startLevel = 1;
        this.state.treedataCol = {};
        if (typeof treedata === 'object') {
            if (treedata.constructor.name === 'Array') {
                startLevel = 0;
                treedata = { children:treedata };
            }
        }
        this.processTreedataItem(treedata, startLevel);
        return treedata;
    },

    processTreedataItem: function(item, level) {
        item.level = level;
        item.uid = this.generateUid();
        item.selectStatus = 'n'; // select status can be all | partial | none
        item.isExpanded = (level < this.state.expandLevel) || false;
        item.hasChildren = (item.children && item.children.length > 0) || false;
        if (level > 0) {
            this.state.treedataCol[item.uid] = item;
        }
        if (item.children) {
            for (var i = 0; i < item.children.length; i++) {
                var childItem = item.children[i];
                this.processTreedataItem(childItem, level + 1);
            }
        }
    },

    /*
    Getting nodes from treedata item
    Example input treedata:
    [
        {
            "name": "Top Level",
            "children": [
                {
                    "name": "Level 2: A",
                    "children": [
                        {
                            "name": "Son of A"
                        },
                        {
                            "name": "Daughter of A"
                        }
                    ]
                },
                {
                    "name": "Level 2: B"
                },
                {
                    "name": "Level 2: C",
                    "children": [
                        {
                            "name": "Son of C"
                        }
                    ]
                }
            ]
        }
    ]
    output nodes:
    [
        { display:'Top Level', level:1 },
        { display:'Level 2: A', level:2 },
        { display:'Son of A', level:3 },
        { display:'Daughter of A', level:3 },
        { display:'Level 2: B', level:2 },
        { display:'Level 2: C', level:2 },
        { display:'Son of C', level:3 }
    ]
    */
    getNodesFromTreeItem: function(item) {
        var nodes = [];
        var currentNode = { isExpanded:true };
        var display = item.name || item.text || 'NA';
        // skip level 0, it is artificial object for array input
        if (item.level > 0) {
            currentNode = {
                uid: item.uid,
                item: item,
                display: display,
                level: item.level,
                hasChildren: item.hasChildren,
                isExpanded: item.isExpanded,
                selectStatus: item.selectStatus,
                boxClass: item.boxClass
            };
            nodes.push(currentNode);
        }
        if (item.children && currentNode.isExpanded) {
            var childNodes = [];
            for (var i = 0; i < item.children.length; i++) {
                var childItem = item.children[i];
                var childNodes = this.getNodesFromTreeItem(childItem);
                nodes = nodes.concat(childNodes);
            }
        }
        return nodes;
    },

    onClick: function(event) {
        var target = $(event.target);
        // get cellId from cell icon  or from cell container
        var cellId = target.attr('data-uid');
        if (target.hasClass('treeview-cell-expand-container')) {
            cellId = target.find('i').attr('data-uid');
        }
        if (!cellId) {
            return;
        }
        // cellId format: "<nodeKey>-<cellType>"
        // cellId example: "36c2b148338c8d7a7f99ef6b881e281f-expand"
        var parts = cellId.split('-');
        var nodeKey = parts[0];
        var cellType = parts[1];
        var treedataItem = this.state.treedataCol[nodeKey];
        // change this.state.treedata according to type of icon clicked
        switch (cellType) {
            case 'expand':
                treedataItem.isExpanded = !treedataItem.isExpanded;
                break;
            case 'icon':
                break;
            case 'select':
                switch(treedataItem.selectStatus) {
                case 'a':
                    treedataItem.selectStatus = 'n';
                    break;
                case 'p':
                    treedataItem.selectStatus = 'a';
                    break;
                case 'n':
                    treedataItem.selectStatus = 'a';
                    break;
                }
                break;
        }
        this.state.nodes = this.getNodesFromTreeItem(this.state.treedataObject);
        // update display
        this.forceUpdate();
    },

    // set data for display
    setData: function(treedata) {
        this.state.treedata = treedata;
        this.state.treedataObject = this.prepareTreedata(this.state.treedata);
        this.state.nodes = this.getNodesFromTreeItem(this.state.treedataObject);
        this.forceUpdate();
    },

    // get selected items
    getSelected: function() {
        var resultItems = [];
        var domNode = this.getDOMNode();
        var selectedIcons = $(domNode).find('.treeview-cell-select-container .fa-check-square-o');
        for (var i = 0; i < selectedIcons.length; i ++) {
            var uid = $(selectedIcons[i]).attr('data-uid').split('-')[0];
            var item = this.state.treedataCol[uid];
            resultItems.push(item);
        }
        return resultItems;
    },

    render: function() {
        var treenodes = [];
        for (var i = 0; i < this.state.nodes.length; i++) {
            var node = this.state.nodes[i];
            // need to include node properties tied with UI change, for example: isExpanded
            var nodeKey = 'treenode-' + node.uid;
            nodeKey += '-' + node.selectStatus;
            nodeKey += '-' + (node.isExpanded ? 'e' : 'n');
            treenodes.push(<TreeviewNode data={ node } key={ nodeKey } />);
        }
        return (

                    { treenodes }

        );
    }
});

// TreeviewNode component
var TreeviewNode = React.createClass({
    name: 'treeview-node',
    mixins: [getCommonMixin],

    // attribute definitions
    getAttributes: function() {
        var attributes = [
            { name:'boxClass', type:'string', required:false, defaultValue:'', note:'container CSS class' },
            { name:'uid', type:'string', required:false, defaultValue:'', note:'unique id for node' },
            { name:'level', type:'number', required:false, defaultValue:1, note:'node level' },
            { name:'display', type:'string', required:false, defaultValue:'', note:'text display' },
            { name:'hasChildren', type:'boolean', required:false, defaultValue:false, note:'has children flag' },
            { name:'isExpanded', type:'boolean', required:false, defaultValue:false, note:'is expanded flag' },
            { name:'selectStatus', type:'boolean', required:false, defaultValue:'n', note:'selecte state all/partial/none' },
            { name:'item', type:'object', required:false, defaultValue:'', note:'original data item' }
        ];
        return attributes;
    },

    /*
    onClick: function(event) {
        var element = event.currentTarget;
        var uid = $(element).attr('data-id');
        console.log('clicked on node - uid:', uid, ' data:', this.state.item);
    },
    */

    render: function() {
        var items = [];
        var cells = [];
        var level = this.state.level;

        // add branch cells
        for (var i = 1; i < level; i++) {
            var cellData = { text:i, type:'space' };
            var cellKey = 'treeview-text-cell-space-' + i;
            cells.push(
                <TreeviewCell data={ cellData } key={ cellKey } />
            );
        }

        // add expand cell if there are children
        var expandCellData = {
            uid: this.state.uid + '-expand',
            type: 'expand',
            iconClass: this.state.isExpanded ? 'fa fa-caret-down' : 'fa fa-caret-right'
        };
        if (!this.state.hasChildren) {
            expandCellData.iconClass = 'fa';
        }
        cells.push(<TreeviewCell data={ expandCellData } key='treeview-expand-cell' />);

        // add select cell
        var iconClass = '';
        switch(this.state.selectStatus) {
        case 'a':
            iconClass = 'fa fa-check-square-o';
            break;
        case 'p':
            iconClass = 'fa fa-delicious';
            break;
        case 'n':
            iconClass = 'fa fa-square-o';
            break;
        }
        var selectCellData = {
            uid: this.state.uid + '-select',
            type: 'select',
            iconClass: iconClass
        };
        var selectCellKey = 'treeview-select-cell';
        cells.push(<TreeviewCell data={ selectCellData } key={ selectCellKey } />);

        // only add icon cell when iconClass is present
        if (this.state.item.iconClass) {
            var iconCellData = {
                uid: this.state.uid + '-icon',
                type: 'icon',
                iconClass: this.state.item.iconClass
            };
            cells.push(<TreeviewCell data={ iconCellData } key='treeview-icon-cell' />);
        }

        // add textbox
        var textboxData = {
            uid: this.state.uid + '-textbox',
            type: 'text',
            text: this.state.display
        };
        cells.push(<TreeviewTextBox data={ textboxData } key='treeview-textbox' />);

        return (

                { cells }

        );
    }
});

// TreeviewCell component
var TreeviewCell = React.createClass({
    name: 'treeview-cell',
    mixins: [getCommonMixin],

    // attribute definitions
    getAttributes: function() {
        var attributes = [
            { name:'boxClass', type:'string', required:false, defaultValue:'', note:'container CSS class' },
            { name:'iconClass', type:'string', required:false, defaultValue:'', note:'icon CSS class' },
            { name:'uid', type:'string', required:false, defaultValue:'', note:'unique id for cell' },
            { name:'type', type:'string', required:false, defaultValue:'', note:'treeview type' }
        ];
        return attributes;
    },

    render: function() {
        // set iconClass
        var displayText = '';
        var iconClass = this.state.iconClass || '';
        var iconContainerClass = 'treeview-cell-' + this.state.type + '-container';
        switch (this.state.type) {
            case 'text':
                displayText = this.state.text;
                break;
            case 'expand':
                iconClass = iconClass || 'fa fa-plus-square-o';
                break;
            case 'select':
                iconClass = iconClass || 'fa fa-check-square-o';
                break;
            case 'icon':
                iconClass = iconClass || '';
                break;
        }
        return (

                    { displayText }

        );
    }
});

// Treeview textbox component
var TreeviewTextBox = React.createClass({
    name: 'treeview-textbox',
    mixins: [getCommonMixin],

    // attribute definitions
    getAttributes: function() {
        var attributes = [
            { name:'boxClass', type:'string', required:false, defaultValue:'', note:'container CSS class' },
            { name:'uid', type:'string', required:false, defaultValue:'', note:'unique id for cell' },
            { name:'text', type:'string', required:false, defaultValue:'', note:'display text' },
            { name:'type', type:'string', required:false, defaultValue:'', note:'treeview type' }
        ];
        return attributes;
    },

    render: function() {
        // set content
        var displayText = this.state.text;
        return (

        );
    }
});
<|endoftext|>