marklet

0.0.2 • Public • Published

Marklet

Helper framework for bookmarklets that load external scripts and/or stylesheets. Loading external scripts that depend on each other can be tricky if the scripts must execute in a specific order. A good example of this (which motivated me to create this tool) would be trying to use jQuery and jQuery plugins, as jQuery must be loaded before the plugins. Marklet can help.

Installation

If you don't have NPM, you can open marklet_template_callback.js or marklet_template_promise.js using Github. Or you can use NPM to do a local install and open those files, with:

npm install marklet

If you'd like to use the command line tool to build your scripts for you, do a global install:

npm install marklet -g

Usage

Wrap your bookmarklet code into the marklet function, using either a callback or a promise.

javascript:(function(){
    
    var options = { /* Define marklet options here */ };
    var codeToRun = function(deleter){
        /* Your main bookmarklet code here */
    };
    
    marklet(options, codeToRun);
    
    /* The marklet source code goes here */
})();
    

or

javascript:(function(){
    var options = { /* Define marklet options here */ };
    
    marklet(options).then(function(deleter){
        /* Your main bookmarklet code here */
    }).catch(function(){
        /* Error handling here */
    });
    
    /* The marklet source code goes here */
})();

(Note that if you use both a callback and a promise, the callback will execute first.)

The scripts and styles to be included are defined in the options as include trees, explained below.

deleter() is a function supplied to your code by Marklet. When it is called, all tags added by Marklet will be deleted. This will return the page close to its previous state (though it will not remove any global variables added by the included scripts or your code). This is optional, but good practice.

Build

To build your bookmarklet using Marklet, there are two options: copy/paste, or use the command-line tool. If you are using a local NPM install of Marklet or downloading direct from Github, use the copy/paste method. If you are using a global NPM install, use the command line interface.

Copy/Paste

Open up either the file marklet_template_callback.js or marklet_template_promise.js, and replace your code as directed by the comments. Your final bookmarklet should follow one of the two formats above.

Command Line Tool

Or, if you have NPM installed and Marklet installed globally, then you can use the command line interface.

First ensure that your code is compatible with the format above. Marklet will take care of the wrapping, but it is your job to set Marklet options and call the marklet function. Ex:

var options = { /* Define marklet options here */ };
var codeToRun = function(deleter){
    /* Your main bookmarklet code here */
};
    
marklet(options, codeToRun);

The CLI has the following format:

$ marklet <source file> [destination file]

<source file> must be the file name of the your code. [destination file] can be the desired output file name. [destination file] is optional, and if it is omitted, Marklet will create a file with the name <source file>_marklet.js in the same directory. The file created will be a minified file, which includes the javascript:(function(){})(); bookmarklet wrapper.

Include Trees

Each include entry should include at minimum a URL and an ID. This ID will become the ID for the <script> or <link> tags.

var options = {};
 
options.scripts = {
    url:"//a/url/here.js",
    id:"myScript"
};
 
options.styles = {
    url:"//another/url/here.css",
    id:"myStyle"
};

Chain entries together in arrays to load non-dependent includes in any order:

options.scripts = [
    {
        url:"//url/one.js",
        id:"scriptOne"
    },
    {
        url:"//url/alpha.js",
        id:"scriptAlpha"
    }
];

To force an include to wait until a previous include has finished, branch off using a then key:

options.scripts = {
    url:"//url/one.js",
    id:"scriptOne",
    then:{
        url:"//url/two.js",
        id:"scriptTwo"
    }
};

You can also use arrays in branches, to indicate parallel dependent branches:

options.scripts = {
    url:"//url/one.js",
    id:"scriptOne",
    then:[
        {
            url:"//url/two.js",
            id:"scriptTwo"
        },
        {
            url:"//url/beta.js",
            id:"scriptBeta"
        }
    ]
};

Mix and match the above until you create the loading sequence that you want!

Fallbacks

Marklet provides fallback options in case a resource fails to load.

Use backupUrl to specify an alternate URL to try.

options.styles = {
    url:"//url/one.js",
    backupUrl:"//url/oneBackup.js",
    id:"scriptOne",
    then:{
        url:"//url/two.js",
        id:"scriptTwo"
    }
}
;

Suppose that in the event both of these fail that you'd like to load an entirely different script tree. Use catch.

In the example below, Marklet will first try to load one.js, or if that fails it will try oneBackup.js. If either of those succeed, it will proceed down the then branch to load the dependent two.js. But if both of those were to fail, then Marklet would proceed down the catch branch instead, trying alpha.js then beta.js.

options.scripts = {
    url:"//url/one.js",
    backupUrl:"//url/oneBackup.js",
    id:"scriptOne",
    then:{
        url:"//url/two.js",
        id:"scriptTwo"
    },
    catch:{
        url:"//url/alpha.js",
        id:"scriptAlpha"
        then:{
            url:"//url/beta.js",
            id:"scriptBeta"
        }
    }
}
;

In the event that a branch fails, Marklet will be aborted, and error handlers will run instead of the main bookmarklet code. However, you can prevent this by setting required to false. In the example below, should two.js fail to load properly, three.js would not be loaded but Marklet would still execute the main bookmarklet code.

options.scripts = {
    url:"//url/one.js",
    id:"scriptOne",
    then:{
        url:"//url/two.js",
        id:"scriptTwo",
        required:false,
        then: {
            url:"//url/three.js",
            id:"scriptThree"
        }
    }
}
;

(Note also that required applies to an include and its branches. In the above example, if two.js were to load but three.js were to fail, Marklet would still execute the main code.)

Conditions

You can supply test conditions in several places to shape the behavior of Marklet.

loadCondition

If you would like an include to wait for a certain condition, provide a loadCondition function. This should be a function that returns true if it is safe to load an include. If the function doesn't return true, then Marklet will wait one tick then test the condition again. You can define a tick length in options, but the default is 100 ms. You can also specify a timeout. If the test has not returned true before the timeout, then the include will fail.

options.scripts = {
    url:"//url/one.js",
    id:"scriptOne",
    loadCondition: function(){
        if (/* test */) return true;
    }
};

skipCondition

To programmatically skip some include branches, add a skipCondition. Suuply a function that will return ``true if the include branch should be skipped. Note that this will skip not just the specific include, but also its then branch.

options.scripts = {
    url:"//url/one.js",
    id:"scriptOne",
    skipCondition: function(){
        if (/* test */) return true;
    }
};

codeRunCondition

This is a global option, a condition that is tested before the main code is allowed to run. Pass in a function that will return true when it is safe for the main bookmarklet code to execute. If the function doesn't return true, then Marklet will try the test again in one tick, until the timeout is reached, at which point it will abort.

options.codeRunCondition = function(){
    if (/* test */) return true;
};

Events

Marklet provides both global and include-level events.

onFetch

This is triggered when the tag is added to the DOM and the browser starts to fetch the resource.

options.stlyles = {
    url:"//url/one.css",
    id:"styleOne",
    onFetch: function(){
        /* Do something */
    }
};

onLoad

This is triggered when an include loads succesfully. Note that it applies to a specific include, rather than the whole branch.

options.scripts = {
    url:"//url/one.js",
    id:"scriptOne",
    onLoad: function(){
        /* Do something */
    }
};

onError

This will be triggered if attempts to load a resource trigger an error event from the browser.

options.scripts = {
    url:"//url/one.js",
    id:"scriptOne",
    onError: function(err){
        /* Do something */
    }
};

onTimeout

This will be triggered if the browser has not given either a load or error event by the timeout (as defined in options.timeout).

options.styles = {
    url:"//url/one.css",
    id:"styleOne",
    onTimeout: function(err){
        /* Do something */
    }
};

onBackup

This will be triggered if the primary URL fails, and Marklet is moving on to a backup URL. (This could come in handy if your backup is a different version of the same script).

options.styles = {
    url:"//url/one.css",
    id:"styleOne",
    onBackup: function(err){
        /* Do something */
    }
};

onFail

This is triggered if both the primary and backup URL fail to load (either timeout or error). Marklet will run your callback, then proceed down the catch branch, if there is one.

options.scripts = {
    url:"//url/one.js",
    id:"scriptOne",
    onFail: function(err){
        /* Do something */
    }
};

onAbort

This is a global callback, that will only run if Marklet aborts. (It is roughly equivalent to marklet().catch())

options.onAbort = function(err){
    /* Deal with whatever went horribly wrong */
};

Local Style

In addition to fetching stylesheets using a URL, Marklet allows you to add CSS text directly. Supply the desired style rules as a string to options.localStyle, and Marklet will create and append a <style> tag in the <head> of the page.

options.localStyle = "div:{ max-height:1px; min-width:8000px; }";

Global Options

The global options and their defaults are listed below:

var options = {
    tickLength:100   /* tick time in ms */
    timeout:10000    /* timeout in ms */
    localStyle:""    /* Local CSS style rules to add */
    localStyleId:"markletLocalCss" /* ID for local style tag */
    logging:false /* Put true for verbose logging */
    rejectIdConflict:true /* If true, Marklet won't create a tag if the given ID is already present */
    codeRunCondition:null /* Condition to test before running main code */
    onAbort:function(err){} /* error event */
};
  • tickLength - if the loadCondition on an include fails, Marklet will retry in one tick. Use this option to define a tick, in milliseconds.
  • timeout - the number of milliseconds until Marklet stops trying to test the loadCondition on an include and aborts (or tries the catch branch, or skips the include if it's not required)
  • localStyle - pass Marklet local CSS text directly. (See "Local Style" above)
  • localStyleId - use this if you want to set a specific ID for the <style> tag Marklet creates for the localStyle.
  • logging - if true, Marklet will add verbose logs to the console as it works. Useful for debugging.
  • rejectIdConflict - if true, Marklet will fail if the ID of the tag it's trying to create is already being used in the document (or try the catch branch or skip un-required includes)
  • codeRunCondition - you can provide function that returns true if it's safe to run the main bookmarklet code. See "Conditions" above.
  • onAbort - you can provide a function that is triggered instead of the main code if Marklet aborts. (It is not guaranteed to have an argument, but if the function is running then you know there was a problem)

Example

Here is the test example (test.js in the example folder). This is the code as it appears before using the command line tool. (Notice it uses both callback and promises).

var options= {
    scripts:[
        {
            url:"//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js",
            backupUrl:"https://code.jquery.com/jquery-3.2.1.min.js",
            id:"jquery",
            onFetch:function(){console.log("jquery on Fetch handler!");},
            onLoad:function(){console.log("jquery on load handler!");},
            onError:function(){console.log("jquery on error handler!");},
            onTimeout:function(){console.log("jquery on timeout handler!");},
            onFail:function(){console.log("jquery on fail handler!");},
            then:[
                {
                    url:"//cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.2.3/jquery-confirm.min.js",
                    id:"jquery-alert",
                    loadCondition:function(){if (typeof $ !== 'undefined') return true;}
                },
                {
                    url:"//cdnjs.cloudflare.com/ajax/libs/Sortable/1.6.0/Sortable.min.js",
                    id:"sortable"
                }
            ],
            catch: {
                url:"https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js",
                id:"angular",
                then:{
                    url:"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.js",
                    id:"chartJS",
                    then:{
                        url:"https://cdnjs.cloudflare.com/ajax/libs/angular-chart.js/1.1.1/angular-chart.js",
                        id:"angularChart"
                    }
                }
            }
        }
    ],
    styles: [
        {
            url:"//cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.2.3/jquery-confirm.min.css",
            id:"alert-style",
            required:false,
        },
        {
            url:"//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css",
            id:"bootstrap",
            onFetch:function(){console.log("bootstrap on Fetch handler!");},
            onLoad:function(){console.log("bootstrap on load handler!");},
            onError:function(){console.log("bootstrap on error handler!");},
            onTimeout:function(){console.log("bootstrap on timeout handler!");},
            onFail:function(){console.log("bootstrap on fail handler!");}
        }
    ],
    localStyle:"div:min-height:100px;",
    codeRunCondition: function(){if (typeof $.alert !== 'undefined') return true},
    logging: true,
    rejectIdConflict: false
};
    
/* the main code to run once the tags are added */
var codeToRun = function()  {
    console.log("I made it!");
};
      
    
marklet(options, codeToRun)
    .then(function(deleterFunction){
        
        console.log("everything is dandy ");  
        
        setTimeout(function(){
            deleterFunction()
            .then(function(){
                console.log("Ran deleter function.");
            }); 
        },30000);
        
    })
    .catch(function(){alert("Oh no, Marklet aborted!");});

Then you could use the command line tool as follows:

$ marklet example/test.js 

And it will create the minified, marklet-ified example/test_marklet.js. Then open that file, copy and paste its contents into your URL bar.

To-Do

  • Add some flags to the CLI, to specify un-minified, or escaped code

Package Sidebar

Install

npm i marklet

Weekly Downloads

0

Version

0.0.2

License

ISC

Last publish

Collaborators

  • slevey087