GM_config_sync

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

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greatest.deepsurf.us/scripts/480183/1282331/GM_config_sync.js

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