Discussions » Development

jMod - New javascript library designed for userscripts

§
Posted: 12.01.2015.
Edited: 28.01.2015.

jMod - New javascript library designed for userscripts

jMod on Github

I recently uploaded my first official release of jMod, a new javascript library designed for userscripts, and this seemed like the place to get some feedback on it.

It's designed to handle a lot of the headaches associated with writing userscripts, and it's packed with a bunch of helpful features that will cut your work in half!

One of my favorite features is that it handles a lot of scope/permission problems (authors using firefox know what i'm talking about).

It can also generate an entire tabbed settings dialog for you! Using your choice of either localStorage (single domain) or GM_storage (multi domain).

I could go on and on about this, but here is the gitgub readme:

jMod

Click Here For Full Documentation
Settings Demo

jMod is a library of useful tools for userscript authors with features ranging from popup notifications, to a full settings dialog generator.

Overview
ToDo / Goals

Overview

Lightweight and versatile, jMod can be loaded anywhere without having to worry about the native CSS affecting the UI. jMod loads a FULLY namespaced bootstrap stylesheet that has been trimmed down to its bare essentials. Thats it! jMod does not depend on jQuery. However, loading a copy of jQuery can enhance several of its features.

jMod can be loaded as a required script in your meta block, or as a script element.

Events

Full List of jMod Events

One of jMod's most useful features is handling loading events for you. When run at "document-start", scripts actually execute before the DOM exists. This prevents you from interacting with the document until it is created. jMod takes care of this for you.

jMod.CSS = 'custom css'; // Added to CSS stack until DOM exists

// Start DOM interactions
function onDOMReadyCB(){
    console.log('onDOMReadyCB');
}
jMod.onDOMReady = onDOMReadyCB;

// jMod fully initialized
function onReadyCB(){
    console.log('onReadyCB');
}
jMod.onReady = onReadyCB;

// Page is ready
function onPageReadyCB(){
    console.log('onPageReadyCB');
}
jMod.onPageReady = onPageReadyCB;

// Page is loaded
function loadCB(){
    console.log('loadCB');
}
jMod.load = loadCB;

The following four methods are all functionally equivalent:

// Execute function when jMod is fully loaded and CSS is added
jMod.onReady = function(){
    console.log('onReady');
}
jMod(function(){
    console.log('onReady');
});
jMod('onReady', function(){
    console.log('onReady');
});
jMod.Events.addListener('onReady', function(){
    console.log('onReady');
}, true);
jMod Event Log
jMod Event Log



Settings

Settings Demo
Tutorial

jMod Settings Example
jMod Settings Example



Notifications

jMod Notifications Example
jMod Notifications Example

jQuery

Although jMod is designed to run without using jQuery, there are a few jQuery specific enhancements built in.

GM_xmlhttpRequest in jQuery Ajax Requests

jMod can extend any instance of jQuery to use GM_xmlhttpRequest as its default data transmission method. This allows you to reliably make cross-origin requests without any additionally flags. Doing this affects every ajax request made by jQuery.

Documentation

if($){
    $(document).ready(function() {
        function test_jQueryFunctions(){
            jMod.jQueryExtensions.addCrossOriginSupport($);

            // Test $.ajax()
            console.log('Test $.ajax("http://google.com")');
            $.ajax({
                    url: 'http://google.com',
                    contentType: 'text/html',
                    type: 'GET',
                    dataType: 'html',
                    onprogress: function(response){
                        console.log('onprogress response', response);
                    },
                    onreadystatechange: function(response){
                        console.log('onreadystatechange response', response);
                    }
                })
                .done(function(data, textStatus, jqXHR) {
                    console.log("$.ajax() success: ", jqXHR);
                })
                .fail(function() {
                    console.log("$.ajax() error");
                });

            // Test $(element).load()
            console.log('Test $(element).load("http://google.com #hplogo")');
            var tmpDiv = document.createElement('div');
            tmpDiv.id = 'tmpDiv';
            document.body.appendChild(tmpDiv);

            $('#tmpDiv').load('http://google.com #hplogo', function(responseText, textStatus, jqXHR){
                console.log('$(element).load() ' + textStatus, jqXHR);
            });
        }

        test_jQueryFunctions();
    });
} else {
    console.log('Test Failed! No jQuery');
}

CSS

jMod loads in fully namespaced versions of several popular CSS libraries. They will not interact or change the web page in any way until the namespace is added to an element.

Bootstrap 3.3.2

The bootstrap stylesheet is namespaced with the class ".jmod-na". Many of its standard components have been removed, while others have been heavily modified. For example, tooltip classes have been renamed to avoid having the content page's Bootstrap instance try and interact with it.

Font Awesome

The bootstrap stylesheet is namespaced with the class ".jmod-fa", and defines the font-face as "jModFontAwesome". It doesn't use the same namespace as the other libraries to avoid overriding a page's font awesome instance when doing so is undesirable.

Libraries Used

§
Posted: 12.01.2015.

Looks awesome, stared on GitHub :3

§
Posted: 13.01.2015.

Looks awesome, stared on GitHub :3

Thanks for the support :)

Do you have any suggestions/ideas/requests? It's still a work in progress, but I am always looking for new ideas.

§
Posted: 20.01.2015.

I found that for me one of the most challenging parts about scripting so far, was to detect that another script has altered/updated the page, and that is finished doing that.

I alter/enhance a table that gets updated by another script and eventually found a solution using the MutationObserver.

A wrapper for that, with a callback could be a nice feature.

§
Posted: 20.01.2015.
I found that for me one of the most challenging parts about scripting so far, was to detect that another script has altered/updated the page, and that is finished doing that. I alter/enhance a table that gets updated by another script and eventually found a solution using the MutationObserver. A wrapper for that, with a callback could be a nice feature.

Well what exactly would you want the wrapper to do, apart from creating the observer?

Do you want something like a filter that fires a callback under the right conditions for each record?

For Example:

var target = document.querySelector('#some-id');
var myObserver = new jMod.Observer();

myObserver.addFilter(
    function(){ // Callback Function
        console.log('Filter 1 Fired');
    },
    {
        type: ['attributes', 'childList'], // (optional) Only fires on changes to attributes or childList
        target: {
            hasClass: ['className1', 'className2'], // (optional) Only fires when target has all of the given classes
            hasChildren: ['#queryId > .class'], // (optional) Only fires when all queries return at least 1 element
            // etc...
        },
        addedNodes: {
            // check added nodes
        },
        removedNodes: {
            // check removed nodes
        },
        // etc...
    }
);

myObserver.Observe(target);

Is that the kind of thing you're looking for?

§
Posted: 22.01.2015.

Looks awesome.

Some ideas that I've encuntered: extend local and Greasemonkey storages to store json data by stringifying it. Add sessionStorage also.

How have you dealt with the scope/permission problems? This is a pain in the ass.

Anyway, cheers, this is very very promising. Keep up the good work! :)

§
Posted: 23.01.2015.
Looks awesome. Some ideas that I've encuntered: extend local and Greasemonkey storages to store json data by stringifying it. Add sessionStorage also. How have you dealt with the scope/permission problems? This is a pain in the ass. Anyway, cheers, this is very very promising. Keep up the good work! :)

Storage

sessionStorage Added! It will be available in 0.0.18 (will release in a few days)
sessionStorage Documentation

As for JSON data you can always just stringify it yourself. However, I went ahead and added setJSON and getJSON to all the storage shims. I would just add a method for auto-strigifying objects and parsing values that have a JSON-like structure, but what if a user doesn't want a value parsed? Or what if a user is using a non-standard JSON structure and jMod tries to parse it? I would rather leave those decisions up to the script author.
getJSON Documentation
setJSON Documentation

Permissions

I've dealt with it in a LOT of ways. But most of the time I use a custom CloneInto and ExportFunction shim that I wrote to brute-force cloning.

CloneInto - This is uses the browser's cloneInto function (when available). If it fails (which happens a LOT) it attempts to clone the object property-by-property, dropping any properties that can't be cloned. If that fails, then the original object is returned and you just hope for the best :/

I'm still working on how to clone when cloneInto isn't available.

ExportFunction - Also uses the browser's exportFunction function (when available). Exports a function into the given scope. This is great for callback functions from outside the privileged scope. I use it for JSONP callbacks and when detecting uncaught errors from outside jMod.

These functions are used throughout jMod to avoid potential errors.

extend - Almost identical to jQuery's extend, but will clone scoped objects.

extendp - Same as extend but preserves arrays by merging them instead of replacing the original.

RealTypeOf - Gets the "Real Type" of an object. However, accessing an object's constructor can throw an error if you don't have permission. So it does it safely, temporarily cloning the object if necessary.

log - jMod has a custom logger built in that has a crap ton of useful features. One of which is isolating different loggers in firefox and making sure they all actually log your message. Sometimes if you run your script at document-start, firebug is unavailable and log messages go to the web console. However, once firebug initiates, it gets greedy and doesn't forward messages to the web console. So now you have two consoles, each with half your logs. So the jMod logger makes sure all your loggers get the maximum amount of information. However, logging something scoped can cause problems so it clones everything into the unsafeWindow before sending it to web console/firebug. But this wont work with some error objects; they seem to be completely locked to their scope and you CANNOT use .apply with it as one of the arguments. So there is a second function that can handle sending a scope-locked objects to the consoles (this is really just for logging jMod errors. Not really meant for use by other authors).

jMod handles most everything with the previously mentioned functions, or with methods similar to them (like isArray and isPlainObject use the same methods as RealTypeOf).

GM_xmlhttpRequest in jQuery

The most recent feature I added to jMod is injecting GM_xmlhttpRequest into jQuery. This allows jQuery to reliably make cross origin requests, and you don't event have to add any special flags. This is a godsend for me (and hopefully other script authors) because now you have the full power of jQuery without the limitations of a god damn "Same-origin policy". It is partially available in 0.0.17, but some big changes have been made to it and will be fully available in 0.0.18 as jMod.jQueryExtensions.addCrossOriginSupport.

Final thoughts

Permissions are a pain in firefox, and these are the best solutions I could come up with. I think they are endlessly useful, but I could also be an idiot :P I just hope you can appreciate the work I've put into it. This was originally just supposed to be an API for the userscript hosting site that i've been developing. But it ended up growing into something that I feel has more uses than just reporting errors and statistics to the server.


Anyway, are there any other features you would like to see in a future release? I'm always looking for new ideas.

Post reply

Sign in to post a reply.