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