kopia lustrzana https://github.com/OpenDroneMap/NodeODM
				
				
				
			Options display, sending options to server
							rodzic
							
								
									a769b5425f
								
							
						
					
					
						commit
						185467b0cd
					
				
							
								
								
									
										44
									
								
								index.js
								
								
								
								
							
							
						
						
									
										44
									
								
								index.js
								
								
								
								
							|  | @ -29,7 +29,7 @@ let bodyParser = require('body-parser'); | |||
| let morgan = require('morgan'); | ||||
| let TaskManager = require('./libs/taskManager'); | ||||
| let Task = require('./libs/Task'); | ||||
| let odmOptionsParser = require('./libs/odmOptionsParser'); | ||||
| let odmOptions = require('./libs/odmOptions'); | ||||
| 
 | ||||
| app.use(morgan('tiny')); | ||||
| app.use(bodyParser.urlencoded({extended: true})); | ||||
|  | @ -59,8 +59,18 @@ let upload = multer({ | |||
| app.post('/task/new', addRequestId, upload.array('images'), (req, res) => { | ||||
| 	if (req.files.length === 0) res.json({error: "Need at least 1 file."}); | ||||
| 	else{ | ||||
| 		// Move to data
 | ||||
| 		async.series([ | ||||
| 			cb => {  | ||||
| 				odmOptions.filterOptions(req.body.options, (err, options) => { | ||||
| 					if (err) cb(err); | ||||
| 					else{ | ||||
| 						req.body.options = options; | ||||
| 						cb(null); | ||||
| 					} | ||||
| 				});  | ||||
| 			}, | ||||
| 			 | ||||
| 			// Move uploads to data dir
 | ||||
| 			cb => {  | ||||
| 				fs.stat(`data/${req.id}`, (err, stat) => { | ||||
| 					if (err && err.code === 'ENOENT') cb(); | ||||
|  | @ -70,19 +80,21 @@ app.post('/task/new', addRequestId, upload.array('images'), (req, res) => { | |||
| 			cb => { fs.mkdir(`data/${req.id}`, undefined, cb); }, | ||||
| 			cb => { | ||||
| 				fs.rename(`tmp/${req.id}`, `data/${req.id}/images`, err => { | ||||
| 					if (!err){ | ||||
| 						new Task(req.id, req.body.name, (err, task) => { | ||||
| 							if (err) cb(err); | ||||
| 							else{ | ||||
| 								taskManager.addNew(task); | ||||
| 								res.json({uuid: req.id, success: true}); | ||||
| 								cb(); | ||||
| 							} | ||||
| 						}); | ||||
| 					}else{ | ||||
| 						cb(new Error("Could not move images folder.")) | ||||
| 					} | ||||
| 					if (!err) cb(); | ||||
| 					else cb(new Error("Could not move images folder.")) | ||||
| 				}); | ||||
| 			}, | ||||
| 
 | ||||
| 			// Create task
 | ||||
| 			cb => { | ||||
| 				new Task(req.id, req.body.name, (err, task) => { | ||||
| 					if (err) cb(err); | ||||
| 					else{ | ||||
| 						taskManager.addNew(task); | ||||
| 						res.json({uuid: req.id, success: true}); | ||||
| 						cb(); | ||||
| 					} | ||||
| 				}, req.body.options); | ||||
| 			} | ||||
| 		], err => { | ||||
| 			if (err) res.json({error: err.message}) | ||||
|  | @ -139,7 +151,7 @@ app.post('/task/restart', uuidCheck, (req, res) => { | |||
| }); | ||||
| 
 | ||||
| app.get('/getOptions', (req, res) => { | ||||
| 	odmOptionsParser.getOptions((err, options) => { | ||||
| 	odmOptions.getOptions((err, options) => { | ||||
| 		if (err) res.json({error: err.message}); | ||||
| 		else res.json(options); | ||||
| 	}); | ||||
|  | @ -147,7 +159,7 @@ app.get('/getOptions', (req, res) => { | |||
| 
 | ||||
| let gracefulShutdown = done => { | ||||
| 	async.series([ | ||||
| 		cb => { taskManager.dumpTaskList(cb) }, | ||||
| 		cb => { taskManager.dumpTaskList(cb); }, | ||||
| 		cb => {  | ||||
| 			console.log("Closing server"); | ||||
| 			server.close(); | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ let archiver = require('archiver'); | |||
| let statusCodes = require('./statusCodes'); | ||||
| 
 | ||||
| module.exports = class Task{ | ||||
| 	constructor(uuid, name, done){ | ||||
| 	constructor(uuid, name, done, options = []){ | ||||
| 		assert(uuid !== undefined, "uuid must be set"); | ||||
| 		assert(done !== undefined, "ready must be set"); | ||||
| 
 | ||||
|  | @ -34,10 +34,12 @@ module.exports = class Task{ | |||
| 		this.dateCreated = new Date().getTime(); | ||||
| 		this.processingTime = -1; | ||||
| 		this.setStatus(statusCodes.QUEUED); | ||||
| 		this.options = {}; | ||||
| 		this.options = options; | ||||
| 		this.output = []; | ||||
| 		this.runnerProcess = null; | ||||
| 
 | ||||
| 		this.options.forEach(option => { console.log(option); }); | ||||
| 
 | ||||
| 		// Read images info
 | ||||
| 		fs.readdir(this.getImagesFolderPath(), (err, files) => { | ||||
| 			if (err) done(err); | ||||
|  | @ -64,7 +66,7 @@ module.exports = class Task{ | |||
| 				} | ||||
| 				done(null, task); | ||||
| 			} | ||||
| 		}) | ||||
| 		}, taskJson.options); | ||||
| 	} | ||||
| 
 | ||||
| 	// Get path where images are stored for this task
 | ||||
|  |  | |||
|  | @ -32,8 +32,12 @@ module.exports = { | |||
| 			else{ | ||||
| 				options = {}; | ||||
| 				for (let option in json){ | ||||
| 					if (["-h", "--project-path", "--zip-results"].indexOf(option) !== -1) continue; | ||||
| 					 | ||||
| 					// Not all options are useful to the end user
 | ||||
| 					// (num cores can be set programmatically, so can gcpFile, etc.)
 | ||||
| 					if (["-h", "--project-path",  | ||||
| 						"--zip-results", "--pmvs-num-cores", "--odm_georeferencing-useGcp", | ||||
| 						"--start-with", "--odm_georeferencing-gcpFile", "--end-with"].indexOf(option) !== -1) continue; | ||||
| 
 | ||||
| 					let values = json[option]; | ||||
| 					option = option.replace(/^--/, ""); | ||||
| 
 | ||||
|  | @ -83,5 +87,21 @@ module.exports = { | |||
| 				done(null, options); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	// Checks that the options (as received from the rest endpoint)
 | ||||
| 	// Are valid and within proper ranges.
 | ||||
| 	// The result of filtering is passed back via callback
 | ||||
| 	// @param options[]
 | ||||
| 	filterOptions: function(options, done){ | ||||
| 		try{ | ||||
| 			if (typeof options === "string") options = JSON.parse(options); | ||||
| 
 | ||||
| 			// TODO: range checks, filtering
 | ||||
| 
 | ||||
| 			done(null, options); | ||||
| 		}catch(e){ | ||||
| 			done(e); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | @ -59,4 +59,8 @@ | |||
| 
 | ||||
| .selectric-items li{ | ||||
|     background: #fff; | ||||
| } | ||||
| 
 | ||||
| #options .checkbox{ | ||||
|     margin-right: 143px; | ||||
| } | ||||
|  | @ -1,198 +0,0 @@ | |||
| /*====================================== | ||||
|   Selectric v1.10.1 | ||||
| ======================================*/ | ||||
| 
 | ||||
| .selectric-wrapper { | ||||
|   position: relative; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| .selectric-responsive { | ||||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| .selectric { | ||||
|   border: 1px solid #DDD; | ||||
|   background: #F8F8F8; | ||||
|   position: relative; | ||||
| } | ||||
| .selectric .label { | ||||
|   display: block; | ||||
|   white-space: nowrap; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
|   margin: 0 38px 0 10px; | ||||
|   font-size: 12px; | ||||
|   line-height: 38px; | ||||
|   color: #444; | ||||
|   height: 38px; | ||||
|   -webkit-user-select: none; | ||||
|      -moz-user-select: none; | ||||
|       -ms-user-select: none; | ||||
|           user-select: none; | ||||
| } | ||||
| .selectric .button { | ||||
|   display: block; | ||||
|   position: absolute; | ||||
|   right: 0; | ||||
|   top: 0; | ||||
|   width: 38px; | ||||
|   height: 38px; | ||||
|   color: #BBB; | ||||
|   text-align: center; | ||||
|   font: 0/0 a; | ||||
|   *font: 20px/38px Lucida Sans Unicode, Arial Unicode MS, Arial; | ||||
| } | ||||
| .selectric .button:after { | ||||
|   content: " "; | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   right: 0; | ||||
|   bottom: 0; | ||||
|   left: 0; | ||||
|   margin: auto; | ||||
|   width: 0; | ||||
|   height: 0; | ||||
|   border: 4px solid transparent; | ||||
|   border-top-color: #BBB; | ||||
|   border-bottom: none; | ||||
| } | ||||
| 
 | ||||
| .selectric-focus .selectric { | ||||
|   border-color: #AAA; | ||||
| } | ||||
| 
 | ||||
| .selectric-hover .selectric { | ||||
|   border-color: #C4C4C4; | ||||
| } | ||||
| .selectric-hover .selectric .button { | ||||
|   color: #A2A2A2; | ||||
| } | ||||
| .selectric-hover .selectric .button:after { | ||||
|   border-top-color: #A2A2A2; | ||||
| } | ||||
| 
 | ||||
| .selectric-open { | ||||
|   z-index: 9999; | ||||
| } | ||||
| .selectric-open .selectric { | ||||
|   border-color: #C4C4C4; | ||||
| } | ||||
| .selectric-open .selectric-items { | ||||
|   display: block; | ||||
| } | ||||
| 
 | ||||
| .selectric-disabled { | ||||
|   filter: alpha(opacity=50); | ||||
|   opacity: 0.5; | ||||
|   cursor: default; | ||||
|   -webkit-user-select: none; | ||||
|      -moz-user-select: none; | ||||
|       -ms-user-select: none; | ||||
|           user-select: none; | ||||
| } | ||||
| 
 | ||||
| .selectric-hide-select { | ||||
|   position: relative; | ||||
|   overflow: hidden; | ||||
|   width: 0; | ||||
|   height: 0; | ||||
| } | ||||
| .selectric-hide-select select { | ||||
|   position: absolute; | ||||
|   left: -100%; | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .selectric-input { | ||||
|   position: absolute !important; | ||||
|   top: 0 !important; | ||||
|   left: 0 !important; | ||||
|   overflow: hidden !important; | ||||
|   clip: rect(0, 0, 0, 0) !important; | ||||
|   margin: 0 !important; | ||||
|   padding: 0 !important; | ||||
|   width: 1px !important; | ||||
|   height: 1px !important; | ||||
|   outline: none !important; | ||||
|   border: none !important; | ||||
|   *font: 0/0 a !important; | ||||
|   background: none !important; | ||||
| } | ||||
| 
 | ||||
| .selectric-temp-show { | ||||
|   position: absolute !important; | ||||
|   visibility: hidden !important; | ||||
|   display: block !important; | ||||
| } | ||||
| 
 | ||||
| /* Items box */ | ||||
| .selectric-items { | ||||
|   display: none; | ||||
|   position: absolute; | ||||
|   top: 100%; | ||||
|   left: 0; | ||||
|   background: #F8F8F8; | ||||
|   border: 1px solid #C4C4C4; | ||||
|   z-index: -1; | ||||
|   box-shadow: 0 0 10px -6px; | ||||
| } | ||||
| .selectric-items .selectric-scroll { | ||||
|   height: 100%; | ||||
|   overflow: auto; | ||||
| } | ||||
| .selectric-above .selectric-items { | ||||
|   top: auto; | ||||
|   bottom: 100%; | ||||
| } | ||||
| .selectric-items ul, .selectric-items li { | ||||
|   list-style: none; | ||||
|   padding: 0; | ||||
|   margin: 0; | ||||
|   font-size: 12px; | ||||
|   line-height: 20px; | ||||
|   min-height: 20px; | ||||
| } | ||||
| .selectric-items li { | ||||
|   display: block; | ||||
|   padding: 10px; | ||||
|   color: #666; | ||||
|   cursor: pointer; | ||||
| } | ||||
| .selectric-items li.selected { | ||||
|   background: #E0E0E0; | ||||
|   color: #444; | ||||
| } | ||||
| .selectric-items li:hover { | ||||
|   background: #D5D5D5; | ||||
|   color: #444; | ||||
| } | ||||
| .selectric-items .disabled { | ||||
|   filter: alpha(opacity=50); | ||||
|   opacity: 0.5; | ||||
|   cursor: default !important; | ||||
|   background: none !important; | ||||
|   color: #666 !important; | ||||
|   -webkit-user-select: none; | ||||
|      -moz-user-select: none; | ||||
|       -ms-user-select: none; | ||||
|           user-select: none; | ||||
| } | ||||
| .selectric-items .selectric-group .selectric-group-label { | ||||
|   font-weight: bold; | ||||
|   padding-left: 10px; | ||||
|   cursor: default; | ||||
|   -webkit-user-select: none; | ||||
|      -moz-user-select: none; | ||||
|       -ms-user-select: none; | ||||
|           user-select: none; | ||||
|   background: none; | ||||
|   color: #444; | ||||
| } | ||||
| .selectric-items .selectric-group.disabled li { | ||||
|   filter: alpha(opacity=100); | ||||
|   opacity: 1; | ||||
| } | ||||
| .selectric-items .selectric-group li { | ||||
|   padding-left: 25px; | ||||
| } | ||||
|  | @ -15,8 +15,6 @@ | |||
|             } | ||||
|         </style> | ||||
|         <link href="css/fileinput.css" media="all" rel="stylesheet" type="text/css" /> | ||||
|         <link rel="stylesheet" href="css/selectric.css"> | ||||
| 
 | ||||
|         <link rel="stylesheet" href="css/main.css"> | ||||
| 
 | ||||
|         <script src="js/vendor/modernizr-2.8.3.min.js"></script> | ||||
|  | @ -42,22 +40,40 @@ | |||
| 			<br/> | ||||
| 			<form enctype="multipart/form-data" onsubmit="return false;"> | ||||
| 				<div class="form-group form-inline"> | ||||
| 					<label for="taskName">Project Name:</lable> <input type="text" class="form-control" value="" id="taskName"/> | ||||
| 					<label for="taskName">Project Name:</lable> <input type="text" class="form-control" value="" id="taskName" /> | ||||
| 				</div> | ||||
| 				<div class="form-group"> | ||||
|                     <label for="images">Aerial Imageries:</label> <input id="images" name="images" multiple type="file"> | ||||
|                     <div id="errorBlock" class="help-block"></div> | ||||
|                 </div> | ||||
|                 <div class="text-right"><input type="submit" class="btn btn-success" value="Start Task" id="btnUpload" /></div> | ||||
|                 <div class="form-group" id="options"> | ||||
|                    <label>Set Options:</label> | ||||
|                    <div data-bind="visible: error(), text: error()" class="alert alert-warning" role="alert"></div> | ||||
|                    <select data-bind="foreach: options"> | ||||
|                     <option data-bind="text: params.help + ' (--' + name + ')'"></option> | ||||
|                    </select> | ||||
|             <label for="images">Aerial Imageries:</label> <input id="images" name="images" multiple type="file"> | ||||
|             <div id="errorBlock" class="help-block"></div> | ||||
|         </div> | ||||
|         <div class="text-right"><input type="submit" class="btn btn-success" value="Start Task" id="btnUpload" /></div> | ||||
|         <div id="options"> | ||||
|           <div class="form-inline form-group form-horizontal"> | ||||
|              <div data-bind="visible: error(), text: error()" class="alert alert-warning" role="alert"></div> | ||||
|              <button style="position: relative; top: -45px;" type="submit" class="btn btn-default" data-bind="visible: !error(), click: function(){ showOptions(!showOptions()); }, text: (showOptions() ? 'Hide' : 'Show') + ' Options'"></button> | ||||
|              | ||||
|              <div data-bind="visible: showOptions()"> | ||||
|                 <div data-bind="foreach: options"> | ||||
|                  <label data-bind="text: name + (params.domain ? ' (' + params.domain + ')' : '')"></label><br/> | ||||
|                  <!-- ko if: params.type !== 'bool' --> | ||||
|                  <input type="text" class="form-control" data-bind="attr: {placeholder: params.value}, value: value"> | ||||
|                  <!-- /ko --> | ||||
|                  <!-- ko if: params.type === 'bool' --> | ||||
|                  <div class="checkbox"> | ||||
|                   <label> | ||||
|                     <input type="checkbox" data-bind="checked: value"> Enable | ||||
|                   </label> | ||||
|                 </div> | ||||
|                  <!-- /ko --> | ||||
|                  <button type="submit" class="btn glyphicon glyphicon-info-sign btn-info" data-toggle="tooltip" data-placement="top" data-bind="attr: {title: params.help}"></button> | ||||
|                  <button type="submit" class="btn glyphicon glyphicon glyphicon-repeat btn-default" data-toggle="tooltip" data-placement="top" title="Reset to default" data-bind="click: resetToDefault"></button> | ||||
| 
 | ||||
|             </form> | ||||
|                  <br/><br/> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </form> | ||||
| 		</div> | ||||
|         <div class="col-md-7" id="taskList"> | ||||
|           <h2>Current Tasks (<span data-bind="text: tasks().length"></span>)</h2> | ||||
|  | @ -118,7 +134,6 @@ | |||
|         <script src="js/vendor/bootstrap.min.js"></script> | ||||
|         <script src="js/vendor/knockout-3.4.0.js"></script> | ||||
|         <script src="js/fileinput.js" type="text/javascript"></script> | ||||
|         <script src="js/jquery.selectric.js" type="text/javascript"></script> | ||||
| 
 | ||||
|         <script src="js/main.js"></script> | ||||
|     </body> | ||||
|  |  | |||
|  | @ -1,911 +0,0 @@ | |||
| /*! | ||||
|  *         ,/ | ||||
|  *       ,'/ | ||||
|  *     ,' / | ||||
|  *   ,'  /_____, | ||||
|  * .'____    ,' | ||||
|  *      /  ,' | ||||
|  *     / ,' | ||||
|  *    /,' | ||||
|  *   /' | ||||
|  * | ||||
|  * Selectric ϟ v1.10.1 (Jun 30 2016) - http://lcdsantos.github.io/jQuery-Selectric/
 | ||||
|  * | ||||
|  * Copyright (c) 2016 Leonardo Santos; MIT License | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| (function(factory) { | ||||
|   /* global define */ | ||||
|   /* istanbul ignore next */ | ||||
|   if ( typeof define === 'function' && define.amd ) { | ||||
|     define(['jquery'], factory); | ||||
|   } else if ( typeof module === 'object' && module.exports ) { | ||||
|     // Node/CommonJS
 | ||||
|     module.exports = function( root, jQuery ) { | ||||
|       if ( jQuery === undefined ) { | ||||
|         if ( typeof window !== 'undefined' ) { | ||||
|           jQuery = require('jquery'); | ||||
|         } else { | ||||
|           jQuery = require('jquery')(root); | ||||
|         } | ||||
|       } | ||||
|       factory(jQuery); | ||||
|       return jQuery; | ||||
|     }; | ||||
|   } else { | ||||
|     // Browser globals
 | ||||
|     factory(jQuery); | ||||
|   } | ||||
| }(function($) { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var $doc = $(document); | ||||
|   var $win = $(window); | ||||
| 
 | ||||
|   var pluginName = 'selectric'; | ||||
|   var classList = 'Input Items Open Disabled TempShow HideSelect Wrapper Focus Hover Responsive Above Scroll Group GroupLabel'; | ||||
|   var bindSufix = '.sl'; | ||||
| 
 | ||||
|   var chars = ['a', 'e', 'i', 'o', 'u', 'n', 'c', 'y']; | ||||
|   var diacritics = [ | ||||
|     /[\xE0-\xE5]/g, // a
 | ||||
|     /[\xE8-\xEB]/g, // e
 | ||||
|     /[\xEC-\xEF]/g, // i
 | ||||
|     /[\xF2-\xF6]/g, // o
 | ||||
|     /[\xF9-\xFC]/g, // u
 | ||||
|     /[\xF1]/g,      // n
 | ||||
|     /[\xE7]/g,      // c
 | ||||
|     /[\xFD-\xFF]/g  // y
 | ||||
|   ]; | ||||
| 
 | ||||
|   /** | ||||
|    * Create an instance of Selectric | ||||
|    * | ||||
|    * @constructor | ||||
|    * @param {Node} element - The <select> element | ||||
|    * @param {object}  opts - Options | ||||
|    */ | ||||
|   var Selectric = function(element, opts) { | ||||
|     var _this = this; | ||||
| 
 | ||||
|     _this.element = element; | ||||
|     _this.$element = $(element); | ||||
| 
 | ||||
|     _this.state = { | ||||
|       enabled     : false, | ||||
|       opened      : false, | ||||
|       currValue   : -1, | ||||
|       selectedIdx : -1 | ||||
|     }; | ||||
| 
 | ||||
|     _this.eventTriggers = { | ||||
|       open    : _this.open, | ||||
|       close   : _this.close, | ||||
|       destroy : _this.destroy, | ||||
|       refresh : _this.refresh, | ||||
|       init    : _this.init | ||||
|     }; | ||||
| 
 | ||||
|     _this.init(opts); | ||||
|   }; | ||||
| 
 | ||||
|   Selectric.prototype = { | ||||
|     utils: { | ||||
|       /** | ||||
|        * Detect mobile browser | ||||
|        * | ||||
|        * @return {boolean} | ||||
|        */ | ||||
|       isMobile: function() { | ||||
|         return /android|ip(hone|od|ad)/i.test(navigator.userAgent); | ||||
|       }, | ||||
| 
 | ||||
|       /** | ||||
|        * Escape especial characters in string (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
 | ||||
|        * | ||||
|        * @param  {string} str - The string to be escaped | ||||
|        * @return {string}       The string with the special characters escaped | ||||
|        */ | ||||
|       escapeRegExp: function(str) { | ||||
|         return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
 | ||||
|       }, | ||||
| 
 | ||||
|       /** | ||||
|        * Replace diacritics | ||||
|        * | ||||
|        * @param  {string} str - The string to replace the diacritics | ||||
|        * @return {string}       The string with diacritics replaced with ascii characters | ||||
|        */ | ||||
|       replaceDiacritics: function(str) { | ||||
|         var k = diacritics.length; | ||||
| 
 | ||||
|         while (k--) { | ||||
|           str = str.toLowerCase().replace(diacritics[k], chars[k]); | ||||
|         } | ||||
| 
 | ||||
|         return str; | ||||
|       }, | ||||
| 
 | ||||
|       /** | ||||
|        * Format string | ||||
|        * https://gist.github.com/atesgoral/984375
 | ||||
|        * | ||||
|        * @param  {string} f - String to be formated | ||||
|        * @return {string}     String formated | ||||
|        */ | ||||
|       format: function (f) { | ||||
|         var a = arguments; // store outer arguments
 | ||||
|         return ('' + f) // force format specifier to String
 | ||||
|           .replace( // replace tokens in format specifier
 | ||||
|             /\{(?:(\d+)|(\w+))\}/g, // match {token} references
 | ||||
|             function ( | ||||
|               s, // the matched string (ignored)
 | ||||
|               i, // an argument index
 | ||||
|               p // a property name
 | ||||
|             ) { | ||||
|               return p && a[1] // if property name and first argument exist
 | ||||
|                 ? a[1][p] // return property from first argument
 | ||||
|                 : a[i]; // assume argument index and return i-th argument
 | ||||
|             }); | ||||
|       }, | ||||
| 
 | ||||
|       /** | ||||
|        * Get the next enabled item in the options list. | ||||
|        * | ||||
|        * @param  {object} selectItems - The options object. | ||||
|        * @param  {number}    selected - Index of the currently selected option. | ||||
|        * @return {object}               The next enabled item. | ||||
|        */ | ||||
|       nextEnabledItem: function(selectItems, selected) { | ||||
|         while ( selectItems[ selected = (selected + 1) % selectItems.length ].disabled ) { | ||||
|           // empty
 | ||||
|         } | ||||
|         return selected; | ||||
|       }, | ||||
| 
 | ||||
|       /** | ||||
|        * Get the previous enabled item in the options list. | ||||
|        * | ||||
|        * @param  {object} selectItems - The options object. | ||||
|        * @param  {number}    selected - Index of the currently selected option. | ||||
|        * @return {object}               The previous enabled item. | ||||
|        */ | ||||
|       previousEnabledItem: function(selectItems, selected) { | ||||
|         while ( selectItems[ selected = (selected > 0 ? selected : selectItems.length) - 1 ].disabled ) { | ||||
|           // empty
 | ||||
|         } | ||||
|         return selected; | ||||
|       }, | ||||
| 
 | ||||
|       /** | ||||
|        * Transform camelCase string to dash-case. | ||||
|        * | ||||
|        * @param  {string} str - The camelCased string. | ||||
|        * @return {string}       The string transformed to dash-case. | ||||
|        */ | ||||
|       toDash: function(str) { | ||||
|         return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); | ||||
|       }, | ||||
| 
 | ||||
|       /** | ||||
|        * Calls the events and hooks registered with function name. | ||||
|        * | ||||
|        * @param {string}    fn - The name of the function. | ||||
|        * @param {number} scope - Scope that should be set on the function. | ||||
|        */ | ||||
|       triggerCallback: function(fn, scope) { | ||||
|         var elm = scope.element; | ||||
|         var func = scope.options['on' + fn]; | ||||
| 
 | ||||
|         if ( $.isFunction(func) ) { | ||||
|           func.call(elm, elm, scope); | ||||
|         } | ||||
| 
 | ||||
|         if ( $.fn[pluginName].hooks[fn] ) { | ||||
|           $.each($.fn[pluginName].hooks[fn], function() { | ||||
|             this.call(elm, elm, scope); | ||||
|           }); | ||||
|         } | ||||
| 
 | ||||
|         $(elm).trigger(pluginName + '-' + this.toDash(fn), scope); | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     /** Initializes */ | ||||
|     init: function(opts) { | ||||
|       var _this = this; | ||||
| 
 | ||||
|       // Set options
 | ||||
|       _this.options = $.extend(true, {}, $.fn[pluginName].defaults, _this.options, opts); | ||||
| 
 | ||||
|       _this.utils.triggerCallback('BeforeInit', _this); | ||||
| 
 | ||||
|       // Preserve data
 | ||||
|       _this.destroy(true); | ||||
| 
 | ||||
|       // Disable on mobile browsers
 | ||||
|       if ( _this.options.disableOnMobile && _this.utils.isMobile() ) { | ||||
|         _this.disableOnMobile = true; | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       // Get classes
 | ||||
|       _this.classes = _this.getClassNames(); | ||||
| 
 | ||||
|       // Create elements
 | ||||
|       var input        = $('<input/>', { 'class': _this.classes.input, 'readonly': _this.utils.isMobile() }); | ||||
|       var items        = $('<div/>',   { 'class': _this.classes.items, 'tabindex': -1 }); | ||||
|       var itemsScroll  = $('<div/>',   { 'class': _this.classes.scroll }); | ||||
|       var wrapper      = $('<div/>',   { 'class': _this.classes.prefix, 'html': _this.options.arrowButtonMarkup }); | ||||
|       var label        = $('<span/>',  { 'class': 'label' }); | ||||
|       var outerWrapper = _this.$element.wrap('<div/>').parent().append(wrapper.prepend(label), items, input); | ||||
| 
 | ||||
|       _this.elements = { | ||||
|         input        : input, | ||||
|         items        : items, | ||||
|         itemsScroll  : itemsScroll, | ||||
|         wrapper      : wrapper, | ||||
|         label        : label, | ||||
|         outerWrapper : outerWrapper | ||||
|       }; | ||||
| 
 | ||||
|       _this.$element | ||||
|         .on(_this.eventTriggers) | ||||
|         .wrap('<div class="' + _this.classes.hideselect + '"/>'); | ||||
| 
 | ||||
|       _this.originalTabindex = _this.$element.prop('tabindex'); | ||||
|       _this.$element.prop('tabindex', false); | ||||
| 
 | ||||
|       _this.populate(); | ||||
|       _this.activate(); | ||||
| 
 | ||||
|       _this.utils.triggerCallback('Init', _this); | ||||
|     }, | ||||
| 
 | ||||
|     /** Activates the plugin */ | ||||
|     activate: function() { | ||||
|       var _this = this; | ||||
|       var originalWidth = _this.$element.width(); | ||||
| 
 | ||||
|       _this.utils.triggerCallback('BeforeActivate', _this); | ||||
| 
 | ||||
|       _this.elements.outerWrapper.prop('class', [ | ||||
|         _this.classes.wrapper, | ||||
|         _this.$element.prop('class').replace(/\S+/g, _this.classes.prefix + '-$&'), | ||||
|         _this.options.responsive ? _this.classes.responsive : '' | ||||
|       ].join(' ')); | ||||
| 
 | ||||
|       if ( _this.options.inheritOriginalWidth && originalWidth > 0 ) { | ||||
|         _this.elements.outerWrapper.width(originalWidth); | ||||
|       } | ||||
| 
 | ||||
|       if ( !_this.$element.prop('disabled') ) { | ||||
|         _this.state.enabled = true; | ||||
| 
 | ||||
|         // Not disabled, so... Removing disabled class
 | ||||
|         _this.elements.outerWrapper.removeClass(_this.classes.disabled); | ||||
| 
 | ||||
|         // Remove styles from items box
 | ||||
|         // Fix incorrect height when refreshed is triggered with fewer options
 | ||||
|         _this.$li = _this.elements.items.removeAttr('style').find('li'); | ||||
| 
 | ||||
|         _this.bindEvents(); | ||||
|       } else { | ||||
|         _this.elements.outerWrapper.addClass(_this.classes.disabled); | ||||
|         _this.elements.input.prop('disabled', true); | ||||
|       } | ||||
| 
 | ||||
|       _this.utils.triggerCallback('Activate', _this); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Generate classNames for elements | ||||
|      * | ||||
|      * @return {object} Classes object | ||||
|      */ | ||||
|     getClassNames: function() { | ||||
|       var _this = this; | ||||
|       var customClass = _this.options.customClass; | ||||
|       var classesObj  = {}; | ||||
| 
 | ||||
|       $.each(classList.split(' '), function(i, currClass) { | ||||
|         var c = customClass.prefix + currClass; | ||||
|         classesObj[currClass.toLowerCase()] = customClass.camelCase ? c : _this.utils.toDash(c); | ||||
|       }); | ||||
| 
 | ||||
|       classesObj.prefix = customClass.prefix; | ||||
| 
 | ||||
|       return classesObj; | ||||
|     }, | ||||
| 
 | ||||
|     /** Set the label text */ | ||||
|     setLabel: function() { | ||||
|       var _this = this; | ||||
|       var labelBuilder = _this.options.labelBuilder; | ||||
|       var currItem = _this.lookupItems[_this.state.currValue]; | ||||
| 
 | ||||
|       _this.elements.label.html( | ||||
|         $.isFunction(labelBuilder) | ||||
|           ? labelBuilder(currItem) | ||||
|           : _this.utils.format(labelBuilder, currItem) | ||||
|       ); | ||||
|     }, | ||||
| 
 | ||||
|     /** Get and save the available options */ | ||||
|     populate: function() { | ||||
|       var _this = this; | ||||
|       var $options = _this.$element.children(); | ||||
|       var $justOptions = _this.$element.find('option'); | ||||
|       var selectedIndex = $justOptions.index($justOptions.filter(':selected')); | ||||
|       var currIndex = 0; | ||||
| 
 | ||||
|       _this.state.currValue = (_this.state.selected = ~selectedIndex ? selectedIndex : 0); | ||||
|       _this.state.selectedIdx = _this.state.currValue; | ||||
|       _this.items = []; | ||||
|       _this.lookupItems = []; | ||||
| 
 | ||||
|       if ( $options.length ) { | ||||
|         // Build options markup
 | ||||
|         $options.each(function(i) { | ||||
|           var $elm = $(this); | ||||
| 
 | ||||
|           if ( $elm.is('optgroup') ) { | ||||
| 
 | ||||
|             var optionsGroup = { | ||||
|               element       : $elm, | ||||
|               label         : $elm.prop('label'), | ||||
|               groupDisabled : $elm.prop('disabled'), | ||||
|               items         : [] | ||||
|             }; | ||||
| 
 | ||||
|             $elm.children().each(function(i) { | ||||
|               var $elm = $(this); | ||||
|               var optionText = $elm.html(); | ||||
| 
 | ||||
|               optionsGroup.items[i] = { | ||||
|                 index    : currIndex, | ||||
|                 element  : $elm, | ||||
|                 value    : $elm.val(), | ||||
|                 text     : optionText, | ||||
|                 slug     : _this.utils.replaceDiacritics(optionText), | ||||
|                 disabled : optionsGroup.groupDisabled | ||||
|               }; | ||||
| 
 | ||||
|               _this.lookupItems[currIndex] = optionsGroup.items[i]; | ||||
| 
 | ||||
|               currIndex++; | ||||
|             }); | ||||
| 
 | ||||
|             _this.items[i] = optionsGroup; | ||||
| 
 | ||||
|           } else { | ||||
| 
 | ||||
|             var optionText = $elm.html(); | ||||
| 
 | ||||
|             _this.items[i] = { | ||||
|               index    : currIndex, | ||||
|               element  : $elm, | ||||
|               value    : $elm.val(), | ||||
|               text     : optionText, | ||||
|               slug     : _this.utils.replaceDiacritics(optionText), | ||||
|               disabled : $elm.prop('disabled') | ||||
|             }; | ||||
| 
 | ||||
|             _this.lookupItems[currIndex] = _this.items[i]; | ||||
| 
 | ||||
|             currIndex++; | ||||
| 
 | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         _this.setLabel(); | ||||
|         _this.elements.items.append( _this.elements.itemsScroll.html( _this.getItemsMarkup(_this.items) ) ); | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Generate options markup | ||||
|      * | ||||
|      * @param  {object} items - Object containing all available options | ||||
|      * @return {string}         HTML for the options box | ||||
|      */ | ||||
|     getItemsMarkup: function(items) { | ||||
|       var _this = this; | ||||
|       var markup = '<ul>'; | ||||
| 
 | ||||
|       $.each(items, function(i, elm) { | ||||
|         if ( elm.label !== undefined ) { | ||||
| 
 | ||||
|           markup += _this.utils.format('<ul class="{1}"><li class="{2}">{3}</li>', | ||||
|             $.trim([_this.classes.group, elm.groupDisabled ? 'disabled' : '', elm.element.prop('class')].join(' ')), | ||||
|             _this.classes.grouplabel, | ||||
|             elm.element.prop('label') | ||||
|           ); | ||||
| 
 | ||||
|           $.each(elm.items, function(i, elm) { | ||||
|             markup += _this.getItemMarkup(elm.index, elm); | ||||
|           }); | ||||
| 
 | ||||
|           markup += '</ul>'; | ||||
| 
 | ||||
|         } else { | ||||
| 
 | ||||
|           markup += _this.getItemMarkup(elm.index, elm); | ||||
| 
 | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       return markup + '</ul>'; | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Generate every option markup | ||||
|      * | ||||
|      * @param  {number} i   - Index of current item | ||||
|      * @param  {object} elm - Current item | ||||
|      * @return {string}       HTML for the option | ||||
|      */ | ||||
|     getItemMarkup: function(i, elm) { | ||||
|       var _this = this; | ||||
|       var itemBuilder = _this.options.optionsItemBuilder; | ||||
| 
 | ||||
|       return _this.utils.format('<li data-index="{1}" class="{2}">{3}</li>', | ||||
|         i, | ||||
|         $.trim([ | ||||
|           i === _this.state.currValue  ? 'selected' : '', | ||||
|           i === _this.items.length - 1 ? 'last'     : '', | ||||
|           elm.disabled                 ? 'disabled' : '' | ||||
|         ].join(' ')), | ||||
|         $.isFunction(itemBuilder) ? itemBuilder(elm, elm.element, i) : _this.utils.format(itemBuilder, elm) | ||||
|       ); | ||||
|     }, | ||||
| 
 | ||||
|     /** Bind events on the elements */ | ||||
|     bindEvents: function() { | ||||
|       var _this = this; | ||||
| 
 | ||||
|       _this.elements.wrapper | ||||
|         .add(_this.$element) | ||||
|         .add(_this.elements.outerWrapper) | ||||
|         .add(_this.elements.input) | ||||
|         .off(bindSufix); | ||||
| 
 | ||||
|       _this.elements.outerWrapper.on('mouseenter' + bindSufix + ' mouseleave' + bindSufix, function(e) { | ||||
|         $(this).toggleClass(_this.classes.hover, e.type === 'mouseenter'); | ||||
| 
 | ||||
|         // Delay close effect when openOnHover is true
 | ||||
|         if ( _this.options.openOnHover ) { | ||||
|           clearTimeout(_this.closeTimer); | ||||
| 
 | ||||
|           if ( e.type === 'mouseleave' ) { | ||||
|             _this.closeTimer = setTimeout($.proxy(_this.close, _this), _this.options.hoverIntentTimeout); | ||||
|           } else { | ||||
|             _this.open(); | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       // Toggle open/close
 | ||||
|       _this.elements.wrapper.on('click' + bindSufix, function(e) { | ||||
|         _this.state.opened ? _this.close() : _this.open(e); | ||||
|       }); | ||||
| 
 | ||||
|       _this.elements.input | ||||
|         .prop({ tabindex: _this.originalTabindex, disabled: false }) | ||||
|         .on('keydown' + bindSufix, $.proxy(_this.handleKeys, _this)) | ||||
|         .on('focusin' + bindSufix, function(e) { | ||||
|           _this.elements.outerWrapper.addClass(_this.classes.focus); | ||||
| 
 | ||||
|           // Prevent the flicker when focusing out and back again in the browser window
 | ||||
|           _this.elements.input.one('blur', function() { | ||||
|             _this.elements.input.blur(); | ||||
|           }); | ||||
| 
 | ||||
|           if ( _this.options.openOnFocus && !_this.state.opened ) { | ||||
|             _this.open(e); | ||||
|           } | ||||
|         }) | ||||
|         .on('focusout' + bindSufix, function() { | ||||
|           _this.elements.outerWrapper.removeClass(_this.classes.focus); | ||||
|         }) | ||||
|         .on('input propertychange', function() { | ||||
|           var val = _this.elements.input.val(); | ||||
| 
 | ||||
|           // Clear search
 | ||||
|           clearTimeout(_this.resetStr); | ||||
|           _this.resetStr = setTimeout(function() { | ||||
|             _this.elements.input.val(''); | ||||
|           }, _this.options.keySearchTimeout); | ||||
| 
 | ||||
|           if ( val.length ) { | ||||
|             // Search in select options
 | ||||
|             $.each(_this.items, function(i, elm) { | ||||
|               if ( RegExp('^' + _this.utils.escapeRegExp(val), 'i').test(elm.slug) && !elm.disabled ) { | ||||
|                 _this.select(i); | ||||
|                 return false; | ||||
|               } | ||||
|             }); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|       _this.$li.on({ | ||||
|         // Prevent <input> blur on Chrome
 | ||||
|         mousedown: function(e) { | ||||
|           e.preventDefault(); | ||||
|           e.stopPropagation(); | ||||
|         }, | ||||
|         click: function() { | ||||
|           // The second parameter is to close the box after click
 | ||||
|           _this.select($(this).data('index'), true); | ||||
| 
 | ||||
|           // Chrome doesn't close options box if select is wrapped with a label
 | ||||
|           // We need to 'return false' to avoid that
 | ||||
|           return false; | ||||
|         } | ||||
|       }); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Behavior when keyboard keys is pressed | ||||
|      * | ||||
|      * @param {object} e - Event object | ||||
|      */ | ||||
|     handleKeys: function(e) { | ||||
|       var _this = this; | ||||
|       var key = e.keyCode || e.which; | ||||
|       var keys = _this.options.keys; | ||||
| 
 | ||||
|       var isPrev = $.inArray(key, keys.previous) > -1; | ||||
|       var isNext = $.inArray(key, keys.next) > -1; | ||||
|       var isSelect = $.inArray(key, keys.select) > -1; | ||||
|       var isOpen = $.inArray(key, keys.open) > -1; | ||||
|       var idx = _this.state.selectedIdx; | ||||
|       var isFirstOrLastItem = (isPrev && idx === 0) || (isNext && (idx + 1) === _this.items.length); | ||||
|       var goToItem = 0; | ||||
| 
 | ||||
|       // Enter / Space
 | ||||
|       if ( key === 13 || key === 32 ) { | ||||
|         e.preventDefault(); | ||||
|       } | ||||
| 
 | ||||
|       // If it's a directional key
 | ||||
|       if ( isPrev || isNext ) { | ||||
|         if ( !_this.options.allowWrap && isFirstOrLastItem ) { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         if ( isPrev ) { | ||||
|           goToItem = _this.utils.previousEnabledItem(_this.items, idx); | ||||
|         } | ||||
| 
 | ||||
|         if ( isNext ) { | ||||
|           goToItem = _this.utils.nextEnabledItem(_this.items, idx); | ||||
|         } | ||||
| 
 | ||||
|         _this.select(goToItem); | ||||
|       } | ||||
| 
 | ||||
|       // Tab / Enter / ESC
 | ||||
|       if ( isSelect && _this.state.opened ) { | ||||
|         _this.select(idx, true); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       // Space / Enter / Left / Up / Right / Down
 | ||||
|       if ( isOpen && !_this.state.opened ) { | ||||
|         _this.open(); | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     /** Update the items object */ | ||||
|     refresh: function() { | ||||
|       var _this = this; | ||||
| 
 | ||||
|       _this.populate(); | ||||
|       _this.activate(); | ||||
|       _this.utils.triggerCallback('Refresh', _this); | ||||
|     }, | ||||
| 
 | ||||
|     /** Set options box width/height */ | ||||
|     setOptionsDimensions: function() { | ||||
|       var _this = this; | ||||
| 
 | ||||
|       // Calculate options box height
 | ||||
|       // Set a temporary class on the hidden parent of the element
 | ||||
|       var hiddenChildren = _this.elements.items.closest(':visible').children(':hidden').addClass(_this.classes.tempshow); | ||||
|       var maxHeight = _this.options.maxHeight; | ||||
|       var itemsWidth = _this.elements.items.outerWidth(); | ||||
|       var wrapperWidth = _this.elements.wrapper.outerWidth() - (itemsWidth - _this.elements.items.width()); | ||||
| 
 | ||||
|       // Set the dimensions, minimum is wrapper width, expand for long items if option is true
 | ||||
|       if ( !_this.options.expandToItemText || wrapperWidth > itemsWidth ) { | ||||
|         _this.finalWidth = wrapperWidth; | ||||
|       } else { | ||||
|         // Make sure the scrollbar width is included
 | ||||
|         _this.elements.items.css('overflow', 'scroll'); | ||||
| 
 | ||||
|         // Set a really long width for _this.elements.outerWrapper
 | ||||
|         _this.elements.outerWrapper.width(9e4); | ||||
|         _this.finalWidth = _this.elements.items.width(); | ||||
|         // Set scroll bar to auto
 | ||||
|         _this.elements.items.css('overflow', ''); | ||||
|         _this.elements.outerWrapper.width(''); | ||||
|       } | ||||
| 
 | ||||
|       _this.elements.items.width(_this.finalWidth).height() > maxHeight && _this.elements.items.height(maxHeight); | ||||
| 
 | ||||
|       // Remove the temporary class
 | ||||
|       hiddenChildren.removeClass(_this.classes.tempshow); | ||||
|     }, | ||||
| 
 | ||||
|     /** Detect if the options box is inside the window */ | ||||
|     isInViewport: function() { | ||||
|       var _this = this; | ||||
|       var scrollTop = $win.scrollTop(); | ||||
|       var winHeight = $win.height(); | ||||
|       var uiPosX = _this.elements.outerWrapper.offset().top; | ||||
|       var uiHeight = _this.elements.outerWrapper.outerHeight(); | ||||
| 
 | ||||
|       var fitsDown = (uiPosX + uiHeight + _this.itemsHeight) <= (scrollTop + winHeight); | ||||
|       var fitsAbove = (uiPosX - _this.itemsHeight) > scrollTop; | ||||
| 
 | ||||
|       // If it does not fit below, only render it
 | ||||
|       // above it fit's there.
 | ||||
|       // It's acceptable that the user needs to
 | ||||
|       // scroll the viewport to see the cut off UI
 | ||||
|       var renderAbove = !fitsDown && fitsAbove; | ||||
| 
 | ||||
|       _this.elements.outerWrapper.toggleClass(_this.classes.above, renderAbove); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Detect if currently selected option is visible and scroll the options box to show it | ||||
|      * | ||||
|      * @param {number} index - Index of the selected items | ||||
|      */ | ||||
|     detectItemVisibility: function(index) { | ||||
|       var _this = this; | ||||
|       var liHeight = _this.$li.eq(index).outerHeight(); | ||||
|       var liTop = _this.$li[index].offsetTop; | ||||
|       var itemsScrollTop = _this.elements.itemsScroll.scrollTop(); | ||||
|       var scrollT = liTop + liHeight * 2; | ||||
| 
 | ||||
|       _this.elements.itemsScroll.scrollTop( | ||||
|         scrollT > itemsScrollTop + _this.itemsHeight ? scrollT - _this.itemsHeight : | ||||
|           liTop - liHeight < itemsScrollTop ? liTop - liHeight : | ||||
|             itemsScrollTop | ||||
|       ); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Open the select options box | ||||
|      * | ||||
|      * @param {event} e - Event | ||||
|      */ | ||||
|     open: function(e) { | ||||
|       var _this = this; | ||||
| 
 | ||||
|       _this.utils.triggerCallback('BeforeOpen', _this); | ||||
| 
 | ||||
|       if ( e ) { | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
|       } | ||||
| 
 | ||||
|       if ( _this.state.enabled ) { | ||||
|         _this.setOptionsDimensions(); | ||||
| 
 | ||||
|         // Find any other opened instances of select and close it
 | ||||
|         $('.' + _this.classes.hideselect, '.' + _this.classes.open).children()[pluginName]('close'); | ||||
| 
 | ||||
|         _this.state.opened = true; | ||||
|         _this.itemsHeight = _this.elements.items.outerHeight(); | ||||
|         _this.itemsInnerHeight = _this.elements.items.height(); | ||||
| 
 | ||||
|         // Toggle options box visibility
 | ||||
|         _this.elements.outerWrapper.addClass(_this.classes.open); | ||||
| 
 | ||||
|         // Give dummy input focus
 | ||||
|         _this.elements.input.val(''); | ||||
|         if ( e && e.type !== 'focusin' ) { | ||||
|           _this.elements.input.focus(); | ||||
|         } | ||||
| 
 | ||||
|         $doc | ||||
|           .on('click' + bindSufix, $.proxy(_this.close, _this)) | ||||
|           .on('scroll' + bindSufix, $.proxy(_this.isInViewport, _this)); | ||||
|         _this.isInViewport(); | ||||
| 
 | ||||
|         // Prevent window scroll when using mouse wheel inside items box
 | ||||
|         if ( _this.options.preventWindowScroll ) { | ||||
|           /* istanbul ignore next */ | ||||
|           $doc.on('mousewheel' + bindSufix + ' DOMMouseScroll' + bindSufix, '.' + _this.classes.scroll, function(e) { | ||||
|             var orgEvent = e.originalEvent; | ||||
|             var scrollTop = $(this).scrollTop(); | ||||
|             var deltaY = 0; | ||||
| 
 | ||||
|             if ( 'detail'      in orgEvent ) { deltaY = orgEvent.detail * -1; } | ||||
|             if ( 'wheelDelta'  in orgEvent ) { deltaY = orgEvent.wheelDelta;  } | ||||
|             if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY; } | ||||
|             if ( 'deltaY'      in orgEvent ) { deltaY = orgEvent.deltaY * -1; } | ||||
| 
 | ||||
|             if ( scrollTop === (this.scrollHeight - _this.itemsInnerHeight) && deltaY < 0 || scrollTop === 0 && deltaY > 0 ) { | ||||
|               e.preventDefault(); | ||||
|             } | ||||
|           }); | ||||
|         } | ||||
| 
 | ||||
|         _this.detectItemVisibility(_this.state.selectedIdx); | ||||
| 
 | ||||
|         _this.utils.triggerCallback('Open', _this); | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     /** Close the select options box */ | ||||
|     close: function() { | ||||
|       var _this = this; | ||||
| 
 | ||||
|       _this.utils.triggerCallback('BeforeClose', _this); | ||||
| 
 | ||||
|       _this.change(); | ||||
| 
 | ||||
|       // Remove custom events on document
 | ||||
|       $doc.off(bindSufix); | ||||
| 
 | ||||
|       // Remove visible class to hide options box
 | ||||
|       _this.elements.outerWrapper.removeClass(_this.classes.open); | ||||
| 
 | ||||
|       _this.state.opened = false; | ||||
| 
 | ||||
|       _this.utils.triggerCallback('Close', _this); | ||||
|     }, | ||||
| 
 | ||||
|     /** Select current option and change the label */ | ||||
|     change: function() { | ||||
|       var _this = this; | ||||
| 
 | ||||
|       _this.utils.triggerCallback('BeforeChange', _this); | ||||
| 
 | ||||
|       if ( _this.state.currValue !== _this.state.selectedIdx ) { | ||||
|         // Apply changed value to original select
 | ||||
|         _this.$element | ||||
|           .prop('selectedIndex', _this.state.currValue = _this.state.selectedIdx) | ||||
|           .data('value', _this.lookupItems[_this.state.selectedIdx].text); | ||||
| 
 | ||||
|         // Change label text
 | ||||
|         _this.setLabel(); | ||||
|       } | ||||
| 
 | ||||
|       _this.utils.triggerCallback('Change', _this); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Select option | ||||
|      * | ||||
|      * @param {number}  index - Index of the option that will be selected | ||||
|      * @param {boolean} close - Close the options box after selecting | ||||
|      */ | ||||
|     select: function(index, close) { | ||||
|       var _this = this; | ||||
| 
 | ||||
|       // Parameter index is required
 | ||||
|       if ( index === undefined ) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       // If element is disabled, can't select it
 | ||||
|       if ( !_this.lookupItems[index].disabled ) { | ||||
|         _this.$li.filter('[data-index]') | ||||
|           .removeClass('selected') | ||||
|           .eq(_this.state.selectedIdx = index) | ||||
|           .addClass('selected'); | ||||
| 
 | ||||
|         _this.detectItemVisibility(index); | ||||
| 
 | ||||
|         // If 'close' is false (default), the options box won't close after
 | ||||
|         // each selected item, this is necessary for keyboard navigation
 | ||||
|         if ( close ) { | ||||
|           _this.close(); | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Unbind and remove | ||||
|      * | ||||
|      * @param {boolean} preserveData - Check if the data on the element should be removed too | ||||
|      */ | ||||
|     destroy: function(preserveData) { | ||||
|       var _this = this; | ||||
| 
 | ||||
|       if ( _this.state && _this.state.enabled ) { | ||||
|         _this.elements.items.add(_this.elements.wrapper).add(_this.elements.input).remove(); | ||||
| 
 | ||||
|         if ( !preserveData ) { | ||||
|           _this.$element.removeData(pluginName).removeData('value'); | ||||
|         } | ||||
| 
 | ||||
|         _this.$element.prop('tabindex', _this.originalTabindex).off(bindSufix).off(_this.eventTriggers).unwrap().unwrap(); | ||||
| 
 | ||||
|         _this.state.enabled = false; | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   // A really lightweight plugin wrapper around the constructor,
 | ||||
|   // preventing against multiple instantiations
 | ||||
|   $.fn[pluginName] = function(args) { | ||||
|     return this.each(function() { | ||||
|       var data = $.data(this, pluginName); | ||||
| 
 | ||||
|       if ( data && !data.disableOnMobile ) { | ||||
|         (typeof args === 'string' && data[args]) ? data[args]() : data.init(args); | ||||
|       } else { | ||||
|         $.data(this, pluginName, new Selectric(this, args)); | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Hooks for the callbacks | ||||
|    * | ||||
|    * @type {object} | ||||
|    */ | ||||
|   $.fn[pluginName].hooks = { | ||||
|     /** | ||||
|      * @param {string} callbackName - The callback name. | ||||
|      * @param {string}     hookName - The name of the hook to be attached. | ||||
|      * @param {function}         fn - Callback function. | ||||
|      */ | ||||
|     add: function(callbackName, hookName, fn) { | ||||
|       if ( !this[callbackName] ) { | ||||
|         this[callbackName] = {}; | ||||
|       } | ||||
| 
 | ||||
|       this[callbackName][hookName] = fn; | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * @param {string} callbackName - The callback name. | ||||
|      * @param {string}     hookName - The name of the hook that will be removed. | ||||
|      */ | ||||
|     remove: function(callbackName, hookName) { | ||||
|       delete this[callbackName][hookName]; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Default plugin options | ||||
|    * | ||||
|    * @type {object} | ||||
|    */ | ||||
|   $.fn[pluginName].defaults = { | ||||
|     onChange             : function(elm) { $(elm).change(); }, | ||||
|     maxHeight            : 300, | ||||
|     keySearchTimeout     : 500, | ||||
|     arrowButtonMarkup    : '<b class="button">▾</b>', | ||||
|     disableOnMobile      : true, | ||||
|     openOnFocus          : true, | ||||
|     openOnHover          : false, | ||||
|     hoverIntentTimeout   : 500, | ||||
|     expandToItemText     : false, | ||||
|     responsive           : false, | ||||
|     preventWindowScroll  : true, | ||||
|     inheritOriginalWidth : false, | ||||
|     allowWrap            : true, | ||||
|     optionsItemBuilder   : '{text}', // function(itemData, element, index)
 | ||||
|     labelBuilder         : '{text}', // function(currItem)
 | ||||
|     keys                 : { | ||||
|       previous : [37, 38],                 // Left / Up
 | ||||
|       next     : [39, 40],                 // Right / Down
 | ||||
|       select   : [9, 13, 27],              // Tab / Enter / Escape
 | ||||
|       open     : [13, 32, 37, 38, 39, 40], // Enter / Space / Left / Up / Right / Down
 | ||||
|       close    : [9, 27]                   // Tab / Escape
 | ||||
|     }, | ||||
|     customClass          : { | ||||
|       prefix: pluginName, | ||||
|       camelCase: false | ||||
|     } | ||||
|   }; | ||||
| })); | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -149,8 +149,8 @@ $(function(){ | |||
|          }) | ||||
|          .always(function(){ self.loading(false); }); | ||||
|     }; | ||||
|     Task.prototype.consoleMouseOver = function(){ this.autoScrollOutput = false; } | ||||
|     Task.prototype.consoleMouseOut = function(){ this.autoScrollOutput = true; }  | ||||
|     Task.prototype.consoleMouseOver = function(){ this.autoScrollOutput = false; }; | ||||
|     Task.prototype.consoleMouseOut = function(){ this.autoScrollOutput = true; }; | ||||
|     Task.prototype.resetOutput = function(){ | ||||
|         this.viewOutputLine = 0; | ||||
|         this.autoScrollOutput = true; | ||||
|  | @ -170,7 +170,7 @@ $(function(){ | |||
|                     self.viewOutputLine += output.length; | ||||
|                     if (self.autoScrollOutput){ | ||||
|                         var $console = $("#console_" + self.uuid); | ||||
|                         $console.scrollTop($console[0].scrollHeight - $console.height()) | ||||
|                         $console.scrollTop($console[0].scrollHeight - $console.height()); | ||||
|                     } | ||||
|                 } | ||||
|              }) | ||||
|  | @ -243,8 +243,8 @@ $(function(){ | |||
|                 self.info({error: url + " is unreachable."}); | ||||
|                 self.stopRefreshingInfo(); | ||||
|             }); | ||||
|         } | ||||
|     }; | ||||
|         }; | ||||
|     } | ||||
|     Task.prototype.cancel = genApiCall("/task/cancel"); | ||||
|     Task.prototype.restart = genApiCall("/task/restart", function(task){ | ||||
|         task.resetOutput(); | ||||
|  | @ -266,7 +266,8 @@ $(function(){ | |||
|         uploadAsync: false, | ||||
|         uploadExtraData: function(){ | ||||
|             return { | ||||
|                 name: $("#taskName").val() | ||||
|                 name: $("#taskName").val(), | ||||
|                 options: JSON.stringify(optionsModel.getUserOptions()) | ||||
|             }; | ||||
|         } | ||||
|     }); | ||||
|  | @ -299,12 +300,22 @@ $(function(){ | |||
|     function Option(name, params){ | ||||
|         this.name = name; | ||||
|         this.params = params; | ||||
|         this.value = ko.observable(); | ||||
|     } | ||||
|     Option.prototype.resetToDefault = function(){ | ||||
|         this.value(undefined); | ||||
|     }; | ||||
| 
 | ||||
|     function OptionsModel(){ | ||||
|         var self = this; | ||||
| 
 | ||||
|         this.options = ko.observableArray(); | ||||
|         this.options.subscribe(function(){ | ||||
|             setTimeout(function(){ | ||||
|                 $('#options [data-toggle="tooltip"]').tooltip(); | ||||
|             }, 100); | ||||
|         }); | ||||
|         this.showOptions = ko.observable(false); | ||||
|         this.error = ko.observable(); | ||||
| 
 | ||||
|         $.get("/getOptions") | ||||
|  | @ -314,16 +325,25 @@ $(function(){ | |||
|                 for (var optionName in json){ | ||||
|                     self.options.push(new Option(optionName, json[optionName])); | ||||
|                 } | ||||
| 
 | ||||
|                 $('select').selectric({ | ||||
|                   maxHeight: 500 | ||||
|                 }); | ||||
|             } | ||||
|          }) | ||||
|          .fail(function(){ | ||||
|             self.error("options are not available."); | ||||
|          }) | ||||
|          }); | ||||
|     } | ||||
|     OptionsModel.prototype.getUserOptions = function(){ | ||||
|         var result = []; | ||||
|         for (var i = 0; i < this.options().length; i++){ | ||||
|             var opt = this.options()[i]; | ||||
|             if (opt.value() !== undefined){ | ||||
|                 result.push({ | ||||
|                     name: opt.name, | ||||
|                     value: opt.value() | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|         return result; | ||||
|     }; | ||||
| 
 | ||||
|     var optionsModel = new OptionsModel(); | ||||
|     ko.applyBindings(optionsModel, document.getElementById("options")); | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 Piero Toffanin
						Piero Toffanin