GM_config_sizzle

A lightweight, reusable, cross-browser graphical settings framework for inclusion in user scripts.

目前为 2021-01-26 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/420683/894369/GM_config_sizzle.js

  1. /*
  2. Copyright 2009+, GM_config Contributors (https://github.com/sizzlemctwizzle/GM_config)
  3.  
  4. GM_config Contributors:
  5. Mike Medley <medleymind@gmail.com>
  6. Joe Simmons
  7. Izzy Soft
  8. Marti Martz
  9.  
  10. GM_config is distributed under the terms of the GNU Lesser General Public License.
  11.  
  12. GM_config is free software: you can redistribute it and/or modify
  13. it under the terms of the GNU Lesser General Public License as published by
  14. the Free Software Foundation, either version 3 of the License, or
  15. (at your option) any later version.
  16.  
  17. This program is distributed in the hope that it will be useful,
  18. but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. GNU Lesser General Public License for more details.
  21.  
  22. You should have received a copy of the GNU Lesser General Public License
  23. along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. */
  25.  
  26. // ==UserScript==
  27. // @exclude *
  28. // @author Mike Medley <medleymind@gmail.com> (https://github.com/sizzlemctwizzle/GM_config)
  29. // @icon https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config_icon_large.png
  30.  
  31. // ==UserLibrary==
  32. // @name GM_config_sizzle
  33. // @description A lightweight, reusable, cross-browser graphical settings framework for inclusion in user scripts.
  34. // @copyright 2009+, Mike Medley (https://github.com/sizzlemctwizzle)
  35. // @license LGPL-3.0-or-later; https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/LICENSE
  36.  
  37. // ==/UserScript==
  38.  
  39. // ==/UserLibrary==
  40.  
  41.  
  42. // The GM_config constructor
  43. function GM_configStruct() {
  44. // call init() if settings were passed to constructor
  45. if (arguments.length) {
  46. GM_configInit(this, arguments);
  47. this.onInit();
  48. }
  49. }
  50.  
  51. // This is the initializer function
  52. function GM_configInit(config, args) {
  53. // Initialize instance variables
  54. if (typeof config.fields == "undefined") {
  55. config.fields = {};
  56. config.onInit = config.onInit || function() {};
  57. config.onOpen = config.onOpen || function() {};
  58. config.onSave = config.onSave || function() {};
  59. config.onClose = config.onClose || function() {};
  60. config.onReset = config.onReset || function() {};
  61. config.isOpen = false;
  62. config.title = 'User Script Settings';
  63. config.css = {
  64. basic: [
  65. "#GM_config * { font-family: arial,tahoma,myriad pro,sans-serif; }",
  66. "#GM_config { background: #FFF; }",
  67. "#GM_config input[type='radio'] { margin-right: 8px; }",
  68. "#GM_config .indent40 { margin-left: 40%; }",
  69. "#GM_config .field_label { font-size: 12px; font-weight: bold; margin-right: 6px; }",
  70. "#GM_config .radio_label { font-size: 12px; }",
  71. "#GM_config .block { display: block; }",
  72. "#GM_config .saveclose_buttons { margin: 16px 10px 10px; padding: 2px 12px; }",
  73. "#GM_config .reset, #GM_config .reset a," +
  74. " #GM_config_buttons_holder { color: #000; text-align: right; }",
  75. "#GM_config .config_header { font-size: 20pt; margin: 0; }",
  76. "#GM_config .config_desc, #GM_config .section_desc, #GM_config .reset { font-size: 9pt; }",
  77. "#GM_config .center { text-align: center; }",
  78. "#GM_config .section_header_holder { margin-top: 8px; }",
  79. "#GM_config .config_var { margin: 0 0 4px; }",
  80. "#GM_config .section_header { background: #414141; border: 1px solid #000; color: #FFF;",
  81. " font-size: 13pt; margin: 0; }",
  82. "#GM_config .section_desc { background: #EFEFEF; border: 1px solid #CCC; color: #575757;" +
  83. " font-size: 9pt; margin: 0 0 6px; }"
  84. ].join('\n') + '\n',
  85. basicPrefix: "GM_config",
  86. stylish: ""
  87. };
  88. }
  89.  
  90. if (args.length == 1 &&
  91. typeof args[0].id == "string" &&
  92. typeof args[0].appendChild != "function") var settings = args[0];
  93. else {
  94. // Provide backwards-compatibility with argument style intialization
  95. var settings = {};
  96.  
  97. // loop through GM_config.init() arguments
  98. for (var i = 0, l = args.length, arg; i < l; ++i) {
  99. arg = args[i];
  100.  
  101. // An element to use as the config window
  102. if (typeof arg.appendChild == "function") {
  103. settings.frame = arg;
  104. continue;
  105. }
  106.  
  107. switch (typeof arg) {
  108. case 'object':
  109. for (var j in arg) { // could be a callback functions or settings object
  110. if (typeof arg[j] != "function") { // we are in the settings object
  111. settings.fields = arg; // store settings object
  112. break; // leave the loop
  113. } // otherwise it must be a callback function
  114. if (!settings.events) settings.events = {};
  115. settings.events[j] = arg[j];
  116. }
  117. break;
  118. case 'function': // passing a bare function is set to open callback
  119. settings.events = {onOpen: arg};
  120. break;
  121. case 'string': // could be custom CSS or the title string
  122. if (/\w+\s*\{\s*\w+\s*:\s*\w+[\s|\S]*\}/.test(arg))
  123. settings.css = arg;
  124. else
  125. settings.title = arg;
  126. break;
  127. }
  128. }
  129. }
  130.  
  131. /* Initialize everything using the new settings object */
  132. // Set the id
  133. if (settings.id) config.id = settings.id;
  134. else if (typeof config.id == "undefined") config.id = 'GM_config';
  135.  
  136. // Set the title
  137. if (settings.title) config.title = settings.title;
  138.  
  139. // Set the custom css
  140. if (settings.css) config.css.stylish = settings.css;
  141.  
  142. // Set the frame
  143. if (settings.frame) config.frame = settings.frame;
  144.  
  145. // Set the event callbacks
  146. if (settings.events) {
  147. var events = settings.events;
  148. for (var e in events)
  149. config["on" + e.charAt(0).toUpperCase() + e.slice(1)] = events[e];
  150. }
  151.  
  152. // Create the fields
  153. if (settings.fields) {
  154. var stored = config.read(), // read the stored settings
  155. fields = settings.fields,
  156. customTypes = settings.types || {},
  157. configId = config.id;
  158.  
  159. for (var id in fields) {
  160. var field = fields[id];
  161.  
  162. // for each field definition create a field object
  163. if (field)
  164. config.fields[id] = new GM_configField(field, stored[id], id,
  165. customTypes[field.type], configId);
  166. else if (config.fields[id]) delete config.fields[id];
  167. }
  168. }
  169.  
  170. // If the id has changed we must modify the default style
  171. if (config.id != config.css.basicPrefix) {
  172. config.css.basic = config.css.basic.replace(
  173. new RegExp('#' + config.css.basicPrefix, 'gm'), '#' + config.id);
  174. config.css.basicPrefix = config.id;
  175. }
  176. }
  177.  
  178. GM_configStruct.prototype = {
  179. // Support old method of initalizing
  180. init: function() {
  181. GM_configInit(this, arguments);
  182. this.onInit();
  183. },
  184.  
  185. // call GM_config.open() from your script to open the menu
  186. open: function () {
  187. // Die if the menu is already open on this page
  188. // You can have multiple instances but you can't open the same instance twice
  189. var match = document.getElementById(this.id);
  190. if (match && (match.tagName == "IFRAME" || match.childNodes.length > 0)) return;
  191.  
  192. // Sometimes "this" gets overwritten so create an alias
  193. var config = this;
  194.  
  195. // Function to build the mighty config window :)
  196. function buildConfigWin (body, head) {
  197. var create = config.create,
  198. fields = config.fields,
  199. configId = config.id,
  200. bodyWrapper = create('div', {id: configId + '_wrapper'});
  201.  
  202. // Append the style which is our default style plus the user style
  203. head.appendChild(
  204. create('style', {
  205. type: 'text/css',
  206. textContent: config.css.basic + config.css.stylish
  207. }));
  208.  
  209. // Add header and title
  210. bodyWrapper.appendChild(create('div', {
  211. id: configId + '_header',
  212. className: 'config_header block center'
  213. }, config.title));
  214.  
  215. // Append elements
  216. var section = bodyWrapper,
  217. secNum = 0; // Section count
  218.  
  219. // loop through fields
  220. for (var id in fields) {
  221. var field = fields[id],
  222. settings = field.settings;
  223.  
  224. if (settings.section) { // the start of a new section
  225. section = bodyWrapper.appendChild(create('div', {
  226. className: 'section_header_holder',
  227. id: configId + '_section_' + secNum
  228. }));
  229.  
  230. if (Object.prototype.toString.call(settings.section) !== '[object Array]')
  231. settings.section = [settings.section];
  232.  
  233. if (settings.section[0])
  234. section.appendChild(create('div', {
  235. className: 'section_header center',
  236. id: configId + '_section_header_' + secNum
  237. }, settings.section[0]));
  238.  
  239. if (settings.section[1])
  240. section.appendChild(create('p', {
  241. className: 'section_desc center',
  242. id: configId + '_section_desc_' + secNum
  243. }, settings.section[1]));
  244. ++secNum;
  245. }
  246.  
  247. // Create field elements and append to current section
  248. section.appendChild((field.wrapper = field.toNode()));
  249. }
  250.  
  251. // Add save and close buttons
  252. bodyWrapper.appendChild(create('div',
  253. {id: configId + '_buttons_holder'},
  254.  
  255. create('button', {
  256. id: configId + '_saveBtn',
  257. textContent: 'Save',
  258. title: 'Save settings',
  259. className: 'saveclose_buttons',
  260. onclick: function () { config.save() }
  261. }),
  262.  
  263. create('button', {
  264. id: configId + '_closeBtn',
  265. textContent: 'Close',
  266. title: 'Close window',
  267. className: 'saveclose_buttons',
  268. onclick: function () { config.close() }
  269. }),
  270.  
  271. create('div',
  272. {className: 'reset_holder block'},
  273.  
  274. // Reset link
  275. create('a', {
  276. id: configId + '_resetLink',
  277. textContent: 'Reset to defaults',
  278. href: '#',
  279. title: 'Reset fields to default values',
  280. className: 'reset',
  281. onclick: function(e) { e.preventDefault(); config.reset() }
  282. })
  283. )));
  284.  
  285. body.appendChild(bodyWrapper); // Paint everything to window at once
  286. config.center(); // Show and center iframe
  287. window.addEventListener('resize', config.center, false); // Center frame on resize
  288.  
  289. // Call the open() callback function
  290. config.onOpen(config.frame.contentDocument || config.frame.ownerDocument,
  291. config.frame.contentWindow || window,
  292. config.frame);
  293.  
  294. // Close frame on window close
  295. window.addEventListener('beforeunload', function () {
  296. config.close();
  297. }, false);
  298.  
  299. // Now that everything is loaded, make it visible
  300. config.frame.style.display = "block";
  301. config.isOpen = true;
  302. }
  303.  
  304. // Change this in the onOpen callback using this.frame.setAttribute('style', '')
  305. var defaultStyle = 'bottom: auto; border: 1px solid #000; display: none; height: 75%;'
  306. + ' left: 0; margin: 0; max-height: 95%; max-width: 95%; opacity: 0;'
  307. + ' overflow: auto; padding: 0; position: fixed; right: auto; top: 0;'
  308. + ' width: 75%; z-index: 9999;';
  309.  
  310. // Either use the element passed to init() or create an iframe
  311. if (this.frame) {
  312. this.frame.id = this.id; // Allows for prefixing styles with the config id
  313. this.frame.setAttribute('style', defaultStyle);
  314. buildConfigWin(this.frame, this.frame.ownerDocument.getElementsByTagName('head')[0]);
  315. } else {
  316. // Create frame
  317. document.body.appendChild((this.frame = this.create('iframe', {
  318. id: this.id,
  319. style: defaultStyle
  320. })));
  321.  
  322. // In WebKit src can't be set until it is added to the page
  323. this.frame.src = 'about:blank';
  324. // we wait for the iframe to load before we can modify it
  325. this.frame.addEventListener('load', function(e) {
  326. var frame = config.frame;
  327. var body = frame.contentDocument.getElementsByTagName('body')[0];
  328. body.id = config.id; // Allows for prefixing styles with the config id
  329. buildConfigWin(body, frame.contentDocument.getElementsByTagName('head')[0]);
  330. }, false);
  331. }
  332. },
  333.  
  334. save: function () {
  335. var forgotten = this.write();
  336. this.onSave(forgotten); // Call the save() callback function
  337. },
  338.  
  339. close: function() {
  340. // If frame is an iframe then remove it
  341. if (this.frame.contentDocument) {
  342. this.remove(this.frame);
  343. this.frame = null;
  344. } else { // else wipe its content
  345. this.frame.innerHTML = "";
  346. this.frame.style.display = "none";
  347. }
  348.  
  349. // Null out all the fields so we don't leak memory
  350. var fields = this.fields;
  351. for (var id in fields) {
  352. var field = fields[id];
  353. field.wrapper = null;
  354. field.node = null;
  355. }
  356.  
  357. this.onClose(); // Call the close() callback function
  358. this.isOpen = false;
  359. },
  360.  
  361. set: function (name, val) {
  362. this.fields[name].value = val;
  363.  
  364. if (this.fields[name].node) {
  365. this.fields[name].reload();
  366. }
  367. },
  368.  
  369. get: function (name, getLive) {
  370. var field = this.fields[name],
  371. fieldVal = null;
  372.  
  373. if (getLive && field.node) {
  374. fieldVal = field.toValue();
  375. }
  376.  
  377. return fieldVal != null ? fieldVal : field.value;
  378. },
  379.  
  380. write: function (store, obj) {
  381. if (!obj) {
  382. var values = {},
  383. forgotten = {},
  384. fields = this.fields;
  385.  
  386. for (var id in fields) {
  387. var field = fields[id];
  388. var value = field.toValue();
  389.  
  390. if (field.save) {
  391. if (value != null) {
  392. values[id] = value;
  393. field.value = value;
  394. } else
  395. values[id] = field.value;
  396. } else
  397. forgotten[id] = value;
  398. }
  399. }
  400. try {
  401. this.setValue(store || this.id, this.stringify(obj || values));
  402. } catch(e) {
  403. this.log("GM_config failed to save settings!");
  404. }
  405.  
  406. return forgotten;
  407. },
  408.  
  409. read: function (store) {
  410. try {
  411. var rval = this.parser(this.getValue(store || this.id, '{}'));
  412. } catch(e) {
  413. this.log("GM_config failed to read saved settings!");
  414. var rval = {};
  415. }
  416. return rval;
  417. },
  418.  
  419. reset: function () {
  420. var fields = this.fields;
  421.  
  422. // Reset all the fields
  423. for (var id in fields) fields[id].reset();
  424.  
  425. this.onReset(); // Call the reset() callback function
  426. },
  427.  
  428. create: function () {
  429. switch(arguments.length) {
  430. case 1:
  431. var A = document.createTextNode(arguments[0]);
  432. break;
  433. default:
  434. var A = document.createElement(arguments[0]),
  435. B = arguments[1];
  436. for (var b in B) {
  437. if (b.indexOf("on") == 0)
  438. A.addEventListener(b.substring(2), B[b], false);
  439. else if (",style,accesskey,id,name,src,href,which,for".indexOf("," +
  440. b.toLowerCase()) != -1)
  441. A.setAttribute(b, B[b]);
  442. else
  443. A[b] = B[b];
  444. }
  445. if (typeof arguments[2] == "string")
  446. A.innerHTML = arguments[2];
  447. else
  448. for (var i = 2, len = arguments.length; i < len; ++i)
  449. A.appendChild(arguments[i]);
  450. }
  451. return A;
  452. },
  453.  
  454. center: function () {
  455. var node = this.frame;
  456. if (!node) return;
  457. var style = node.style,
  458. beforeOpacity = style.opacity;
  459. if (style.display == 'none') style.opacity = '0';
  460. style.display = '';
  461. style.top = Math.floor((window.innerHeight / 2) - (node.offsetHeight / 2)) + 'px';
  462. style.left = Math.floor((window.innerWidth / 2) - (node.offsetWidth / 2)) + 'px';
  463. style.opacity = '1';
  464. },
  465.  
  466. remove: function (el) {
  467. if (el && el.parentNode) el.parentNode.removeChild(el);
  468. }
  469. };
  470.  
  471. // Define a bunch of API stuff
  472. (function() {
  473. var isGM = typeof GM_getValue != 'undefined' &&
  474. typeof GM_getValue('a', 'b') != 'undefined',
  475. setValue, getValue, stringify, parser;
  476.  
  477. // Define value storing and reading API
  478. if (!isGM) {
  479. setValue = function (name, value) {
  480. return localStorage.setItem(name, value);
  481. };
  482. getValue = function(name, def){
  483. var s = localStorage.getItem(name);
  484. return s == null ? def : s
  485. };
  486.  
  487. // We only support JSON parser outside GM
  488. stringify = JSON.stringify;
  489. parser = JSON.parse;
  490. } else {
  491. setValue = GM_setValue;
  492. getValue = GM_getValue;
  493. stringify = typeof JSON == "undefined" ?
  494. function(obj) {
  495. return obj.toSource();
  496. } : JSON.stringify;
  497. parser = typeof JSON == "undefined" ?
  498. function(jsonData) {
  499. return (new Function('return ' + jsonData + ';'))();
  500. } : JSON.parse;
  501. }
  502.  
  503. GM_configStruct.prototype.isGM = isGM;
  504. GM_configStruct.prototype.setValue = setValue;
  505. GM_configStruct.prototype.getValue = getValue;
  506. GM_configStruct.prototype.stringify = stringify;
  507. GM_configStruct.prototype.parser = parser;
  508. GM_configStruct.prototype.log = window.console ?
  509. console.log : (isGM && typeof GM_log != 'undefined' ?
  510. GM_log : (window.opera ?
  511. opera.postError : function(){ /* no logging */ }
  512. ));
  513. })();
  514.  
  515. function GM_configDefaultValue(type, options) {
  516. var value;
  517.  
  518. if (type.indexOf('unsigned ') == 0)
  519. type = type.substring(9);
  520.  
  521. switch (type) {
  522. case 'radio': case 'select':
  523. value = options[0];
  524. break;
  525. case 'checkbox':
  526. value = false;
  527. break;
  528. case 'int': case 'integer':
  529. case 'float': case 'number':
  530. value = 0;
  531. break;
  532. default:
  533. value = '';
  534. }
  535.  
  536. return value;
  537. }
  538.  
  539. function GM_configField(settings, stored, id, customType, configId) {
  540. // Store the field's settings
  541. this.settings = settings;
  542. this.id = id;
  543. this.configId = configId;
  544. this.node = null;
  545. this.wrapper = null;
  546. this.save = typeof settings.save == "undefined" ? true : settings.save;
  547.  
  548. // Buttons are static and don't have a stored value
  549. if (settings.type == "button") this.save = false;
  550.  
  551. // if a default value wasn't passed through init() then
  552. // if the type is custom use its default value
  553. // else use default value for type
  554. // else use the default value passed through init()
  555. this['default'] = typeof settings['default'] == "undefined" ?
  556. customType ?
  557. customType['default']
  558. : GM_configDefaultValue(settings.type, settings.options)
  559. : settings['default'];
  560.  
  561. // Store the field's value
  562. this.value = typeof stored == "undefined" ? this['default'] : stored;
  563.  
  564. // Setup methods for a custom type
  565. if (customType) {
  566. this.toNode = customType.toNode;
  567. this.toValue = customType.toValue;
  568. this.reset = customType.reset;
  569. }
  570. }
  571.  
  572. GM_configField.prototype = {
  573. create: GM_configStruct.prototype.create,
  574.  
  575. toNode: function() {
  576. var field = this.settings,
  577. value = this.value,
  578. options = field.options,
  579. type = field.type,
  580. id = this.id,
  581. configId = this.configId,
  582. labelPos = field.labelPos,
  583. create = this.create;
  584.  
  585. function addLabel(pos, labelEl, parentNode, beforeEl) {
  586. if (!beforeEl) beforeEl = parentNode.firstChild;
  587. switch (pos) {
  588. case 'right': case 'below':
  589. if (pos == 'below')
  590. parentNode.appendChild(create('br', {}));
  591. parentNode.appendChild(labelEl);
  592. break;
  593. default:
  594. if (pos == 'above')
  595. parentNode.insertBefore(create('br', {}), beforeEl);
  596. parentNode.insertBefore(labelEl, beforeEl);
  597. }
  598. }
  599.  
  600. var retNode = create('div', { className: 'config_var',
  601. id: configId + '_' + id + '_var',
  602. title: field.title || '' }),
  603. firstProp;
  604.  
  605. // Retrieve the first prop
  606. for (var i in field) { firstProp = i; break; }
  607.  
  608. var label = field.label && type != "button" ?
  609. create('label', {
  610. id: configId + '_' + id + '_field_label',
  611. for: configId + '_field_' + id,
  612. className: 'field_label'
  613. }, field.label) : null;
  614.  
  615. switch (type) {
  616. case 'textarea':
  617. retNode.appendChild((this.node = create('textarea', {
  618. innerHTML: value,
  619. id: configId + '_field_' + id,
  620. className: 'block',
  621. cols: (field.cols ? field.cols : 20),
  622. rows: (field.rows ? field.rows : 2)
  623. })));
  624. break;
  625. case 'radio':
  626. var wrap = create('div', {
  627. id: configId + '_field_' + id
  628. });
  629. this.node = wrap;
  630.  
  631. for (var i = 0, len = options.length; i < len; ++i) {
  632. var radLabel = create('label', {
  633. className: 'radio_label'
  634. }, options[i]);
  635.  
  636. var rad = wrap.appendChild(create('input', {
  637. value: options[i],
  638. type: 'radio',
  639. name: id,
  640. checked: options[i] == value
  641. }));
  642.  
  643. var radLabelPos = labelPos &&
  644. (labelPos == 'left' || labelPos == 'right') ?
  645. labelPos : firstProp == 'options' ? 'left' : 'right';
  646.  
  647. addLabel(radLabelPos, radLabel, wrap, rad);
  648. }
  649.  
  650. retNode.appendChild(wrap);
  651. break;
  652. case 'select':
  653. var wrap = create('select', {
  654. id: configId + '_field_' + id
  655. });
  656. this.node = wrap;
  657.  
  658. for (var i = 0, len = options.length; i < len; ++i) {
  659. var option = options[i];
  660. wrap.appendChild(create('option', {
  661. value: option,
  662. selected: option == value
  663. }, option));
  664. }
  665.  
  666. retNode.appendChild(wrap);
  667. break;
  668. default: // fields using input elements
  669. var props = {
  670. id: configId + '_field_' + id,
  671. type: type,
  672. value: type == 'button' ? field.label : value
  673. };
  674.  
  675. switch (type) {
  676. case 'checkbox':
  677. props.checked = value;
  678. break;
  679. case 'button':
  680. props.size = field.size ? field.size : 25;
  681. if (field.script) field.click = field.script;
  682. if (field.click) props.onclick = field.click;
  683. break;
  684. case 'hidden':
  685. break;
  686. default:
  687. // type = text, int, or float
  688. props.type = 'text';
  689. props.size = field.size ? field.size : 25;
  690. }
  691.  
  692. retNode.appendChild((this.node = create('input', props)));
  693. }
  694.  
  695. if (label) {
  696. // If the label is passed first, insert it before the field
  697. // else insert it after
  698. if (!labelPos)
  699. labelPos = firstProp == "label" || type == "radio" ?
  700. "left" : "right";
  701.  
  702. addLabel(labelPos, label, retNode);
  703. }
  704.  
  705. return retNode;
  706. },
  707.  
  708. toValue: function() {
  709. var node = this.node,
  710. field = this.settings,
  711. type = field.type,
  712. unsigned = false,
  713. rval = null;
  714.  
  715. if (!node) return rval;
  716.  
  717. if (type.indexOf('unsigned ') == 0) {
  718. type = type.substring(9);
  719. unsigned = true;
  720. }
  721.  
  722. switch (type) {
  723. case 'checkbox':
  724. rval = node.checked;
  725. break;
  726. case 'select':
  727. rval = node[node.selectedIndex].value;
  728. break;
  729. case 'radio':
  730. var radios = node.getElementsByTagName('input');
  731. for (var i = 0, len = radios.length; i < len; ++i)
  732. if (radios[i].checked)
  733. rval = radios[i].value;
  734. break;
  735. case 'button':
  736. break;
  737. case 'int': case 'integer':
  738. case 'float': case 'number':
  739. var num = Number(node.value);
  740. var warn = 'Field labeled "' + field.label + '" expects a' +
  741. (unsigned ? ' positive ' : 'n ') + 'integer value';
  742.  
  743. if (isNaN(num) || (type.substr(0, 3) == 'int' &&
  744. Math.ceil(num) != Math.floor(num)) ||
  745. (unsigned && num < 0)) {
  746. alert(warn + '.');
  747. return null;
  748. }
  749.  
  750. if (!this._checkNumberRange(num, warn))
  751. return null;
  752. rval = num;
  753. break;
  754. default:
  755. rval = node.value;
  756. break;
  757. }
  758.  
  759. return rval; // value read successfully
  760. },
  761.  
  762. reset: function() {
  763. var node = this.node,
  764. field = this.settings,
  765. type = field.type;
  766.  
  767. if (!node) return;
  768.  
  769. switch (type) {
  770. case 'checkbox':
  771. node.checked = this['default'];
  772. break;
  773. case 'select':
  774. for (var i = 0, len = node.options.length; i < len; ++i)
  775. if (node.options[i].textContent == this['default'])
  776. node.selectedIndex = i;
  777. break;
  778. case 'radio':
  779. var radios = node.getElementsByTagName('input');
  780. for (var i = 0, len = radios.length; i < len; ++i)
  781. if (radios[i].value == this['default'])
  782. radios[i].checked = true;
  783. break;
  784. case 'button' :
  785. break;
  786. default:
  787. node.value = this['default'];
  788. break;
  789. }
  790. },
  791.  
  792. remove: function(el) {
  793. GM_configStruct.prototype.remove(el || this.wrapper);
  794. this.wrapper = null;
  795. this.node = null;
  796. },
  797.  
  798. reload: function() {
  799. var wrapper = this.wrapper;
  800. if (wrapper) {
  801. var fieldParent = wrapper.parentNode;
  802. fieldParent.insertBefore((this.wrapper = this.toNode()), wrapper);
  803. this.remove(wrapper);
  804. }
  805. },
  806.  
  807. _checkNumberRange: function(num, warn) {
  808. var field = this.settings;
  809. if (typeof field.min == "number" && num < field.min) {
  810. alert(warn + ' greater than or equal to ' + field.min + '.');
  811. return null;
  812. }
  813.  
  814. if (typeof field.max == "number" && num > field.max) {
  815. alert(warn + ' less than or equal to ' + field.max + '.');
  816. return null;
  817. }
  818. return true;
  819. }
  820. };
  821.  
  822. // Create default instance of GM_config
  823. var GM_config = new GM_configStruct();