Click here to Skip to main content
15,881,455 members
Articles / Web Development / HTML

Google Chrome Extension - CodeProject Template Items

Rate me:
Please Sign up or sign in to vote.
4.96/5 (28 votes)
29 Aug 2014CPOL11 min read 69.3K   871   54   17
Google Chrome Extension that provides Message and Answer templating

Important V2 Changes

Due to the policy change imposed by Google to protect users from harmful code, there were a number of change required to the extension.

  • Update of the manifest to new V2 standard.
  • Extension distribution from the Chrome Web Store
  • Removal of inline script blocks from HTML pages.

I also took advantage of the changes to upgrade to a new jQuery version.

Introduction

If you are a regular user of CodeProject, or maybe an article moderator, mentor, or even spend your time answering questions in the Q&A section, how many times have you found yourself posting the same thing? How many times have you found yourself heading over to the FAQ list to copy the link to the Article FAQ? Once, twice, ten times? What about going to find a previous post you have made to copy that and use again to save typing it all again?

That is how this utility and article was born. I thought there must be a better way, considered keeping a text file, but discounted that idea, and thought hang on, I'll make another Chrome extension. I've done it once already, so it should be fairly easy to repeat the success of that.

The utility allows the user to create template items that are stored within the browser context, and at the click of a button, insert a selected template item straight into your forum message or answer. You can even just keep small snippets, e.g., a link and be able to quickly add that to your message/answer.

Version 1.1 fixed a couple of bugs, and also introduced a third item type and filtering capabilities within the editor and selector forms.

Topics Covered

In this article, the main items we will look at are:

  • Chrome Background pages and PageActions
  • Chrome Script injection and execution within Browser page context from within Extension context
  • Some JQuery for handling object cloning, item identification

Chrome Extensions - What are they?

Extensions are add-ons for Google Chrome browser, and rather than repeat myself, hop over to my last Chrome Extension Article, where the basics are explained and links to the Chrome Extension and JQuery developer pages can be found. The article can be found here; Chrome Extension - CodeProject Reputation Watcher. If you have never looked at extensions before, I suggest you read this first, as it covers some key extension creation information, such as extension structure, manifest files, packaging and deployment.

NOTE: Google Chrome has changed the way in which packaged extensions can be installed when they are not served by the Chrome Web Store. This is to reduce the risk of drive installs of  dodgy extensions. To use the packaged extension from my website, click the link, then switch to the Extensions Page from the Tools Menu, then drag the file from the download bar at the bottom of the browser onto the Extensions Page. You will see a Drop to Install message appear.  

What About this Extension?

This extension is very similar, the same ideas around the JavaScript files, JQuery references, etc. are all the same. This extension is different from the previous one in the way it operates and triggers the events and interacts with the users page.

To start with, let's take a look at the manifest file for this extension:

JavaScript
{
    "manifest_version": 2,
    "name": "CodeProject Template Items",
    "short_name": "CPTemplates",
    "version": "2.0",
    "content_security_policy": "script-src 'self' https://ssl.google-analytics.com 'unsafe-eval'; object-src 'self' ",
    "description": "Chrome Extension to provide CodeProject template items.",
    "icons": {
        "48": "images/bob48.png",
        "128": "images/bob128.png"
    },
    "background": {
        "scripts": [ "js/background.js" ]
    },
    "page_action": {
        "default_icon": "images/bob.png",
        "default_title": "CodeProject: Insert Template Items",
        "default_popup": "selector.html"
    },
    "options_page": "editor.html",
    "permissions": [
        "tabs",
        "http://*.codeproject.com/"
    ]
}

The usual suspects can be seen, name, version, description, etc., but there are a couple of differences:

  • background - This is the file that runs in the background of the browser, and sets up the browser integration of the extension. Under version 1 this was an html page with inline scripting, but for V2 needed to be extracted to a standalone javascript file.
  • page_action - This sets up an action that can be triggered by the user under the right set of circumstances, the contained properties setup the icon displayed, the tooltip text and the page to display to the user.

You can also see the 'manifest_versions' and 'content_security_policy' changes that were required under the new rules. 

Background Script - What is it Doing?

When the browser starts, the extension background is loaded and any script within is executed. Take a look at the page content below:

JavaScript
    // Check if the address contains the Message Editor URL;
    if (tab.url.indexOf(".codeproject.com/script/Forums/Edit.aspx?") > -1) {
        // ... show the page action.
        chrome.pageAction.show(tabId);
    }

    // Check if the address contains the Question URL;
    if (tab.url.indexOf(".codeproject.com/Questions/") > -1) {
        // ... show the page action.
        chrome.pageAction.show(tabId);
    }
};

// Add a handler to listen for Address Bar changes in the browser, set the callback function

chrome.tabs.onUpdated.addListener(checkForTargetMessageEditorUrl);

When the script loads, it adds a listener to the Browser. This listener waits for the Tabs onUpdated() event and then fires off the callback function checkForTargetMessageEditorUrl(). Chrome will pass a reference to the browser tab that has been updated to the callback. The URL of the tab is checked, and if it contains the addresses of applicable webpages, in this case the Message Editor or a Question Page it will show the page action icon defined in the manifest file within the address region of the browser. If the URL is not one being looked for, then the page action icon is not shown. The page action icon in the address bar is shown below:

CPTemplatesChromeExt/pageActionIcon.png

Now we have a suitable URL checking background code which displays the page action to the user.

The Page Action

Now we have the page action available to the user, when the user clicks the icon, the popup defined in the manifest file is displayed to the user, this is shown below:

CPTemplatesChromeExt/selector_page.png

The user can now click on the template item she/he wants to make use of, and click Select to inject the content into the browser page. We will cover adding and editing items in this list shortly.

How Does the Content Get Injected?

One of the key things to know about Chrome and the user pages and the extension pages is that they are all kept in isolation. The only common area that both the extension and the web page have is the web page DOM, the extension cannot access the script or variables of the page, and the page cannot access the script or variables of the extension. The only common element is the web page DOM, and of course, the hosting Chrome browser.

Let's take a look at the code executed when the user has clicked on a template item and clicks Select:

JavaScript
//-----------------------------------------
//     Button Events - Selector
//-----------------------------------------
function buttonSelect_Click() {
    //
    //Get the current window and then the current tab of that window
    //This should relate to the page on which to do the injection
    chrome.windows.getCurrent(function (theWindow) {
        chrome.tabs.getSelected(theWindow.id, function (theTab) { 
            injectToTab(theTab) });
    });
}

The click event effectively asks Chrome, What is the active Chrome Window? The result of this is a Window object, this is passed into a callback function. Chrome is then asked, What is the active tab of the window you just told me about? Chrome returns a Tab object to another callback function which is then used to inject the content.

JavaScript
function injectToTab(tab) {
    //Build Valid Content for remote execution
    var templateItem = parseInt($("#selectTemplates option:selected").val());
    var subject = templateStore[templateItem].Subject;
    var body = templateStore[templateItem].Body;
    
    // Check if the address contains the Message Editor URL;
    if (tab.url.indexOf(".codeproject.com/script/Forums/Edit.aspx?") > -1) {
        
        //Append the subject
        chrome.tabs.executeScript(tab.id, { code:
            "document.getElementById('ctl00_MC_Subject').value += 
				unescape('" + escape(subject) + "');"
        });
        
        //Append the body
        chrome.tabs.executeScript(tab.id, { code:
            "document.getElementById('ctl00_MC_ContentText_MessageText').value += 
				unescape('" + escape(body) + "');"
        });
    }

    // Check if the address contains the Question URL;
    if (tab.url.indexOf(".codeproject.com/Questions/") > -1) {
        //Append the answer
        chrome.tabs.executeScript(tab.id, { code:
            "document.getElementById
		('ctl00_ctl00_MC_AMC_PostEntryObj_Content_MessageText').value += 
		unescape('" + escape(body) + "');"
        });
    }
}

The function checks to see which item is selected in the list of templates. This then retrieves the item by index from the in memory template array. The code then checks to see which page, based on the URL of the tab, we are injecting into. We then build up a string representation of the script we want to execute within the context of the page and add into it the relevant properties of the template item. When I was developing this, I discovered that newlines within the string do not get handled correctly, so we have to use the JavaScript escape and unescape methods to pass the data correctly.

Once this string is built, Chrome is passed the string representation of the script to then execute within the context of the web page. This script executes and appends the template data to the elements which have been identified by id.

Adding and Storing Template Items

There are two methods of accessing the page used to add/edit template items. The first way is from the Chrome Extensions page, and then click Options the second is by right clicking on the page action icon and selecting Options.

The options page is defined within the manifest in this case editor.html and this page is loaded up. The editor entry page is basically a list of the templates and is shown below:

CPTemplatesChromeExt/editor_select.png

 

You can then click Add, or select an existing template to Edit/Delete/Clone that item.

The browser LocalStorage is used to store a string representation of the template items held in memory. JSON is used to stringify the JavaScript objects and pop this into the local storage, and JSON is also used to convert the string back into the JavaScript object.

When the editor page loads, the storage is initialised, checked if the object has already been defined, if not creates a new empty store. If it already exists, JSON is used to parse the object back into the array.

The code to initialise the storage and parse the objects back into memory is shown below:

JavaScript
function initStorage() {
    if (localStorage["TemplateStore"] == undefined) {
        localStorage["TemplateStore="] = "{}";
    }
    else {
        templateStore = JSON.parse(localStorage["TemplateStore"]);
    }
}

The code to store the array to local storage using JSON to stringify is shown below:

JavaScript
function saveToLocalStorage() {
    //Write out the template store to the local storage
    localStorage["TemplateStore"] = JSON.stringify(templateStore);
}

When the editor loads, if there are existing template items, this array is loaded into the page selection list as option elements using JQuery. The load operation occurs in two stages, the first checks the current page (used by the filtering), the second loads the templates into the HTML, depending the current filter setting, you can also see the index number being written into the option elements.

JavaScript
function loadTemplates() {
    chrome.windows.getCurrent(function (theWindow) {
        chrome.tabs.getSelected(theWindow.id, function (theTab) {
            // Check if the address contains the Message URL;
            if (theTab.url.indexOf(".codeproject.com/script/Forums/Edit.aspx?") > -1) {
                loadTemplatesPart2("Message");
            }
            else {
                // Check if the address contains the Question URL;
                if (theTab.url.indexOf(".codeproject.com/Questions/") > -1) {
                    loadTemplatesPart2("Answer");
                }
                else {
                    loadTemplatesPart2("");
                }
            }
        });
    });
}

function loadTemplatesPart2(urlType) {
    //Clear the list box of any items
    $("#selectTemplates").html(""); //Clear the existing selection box

    if (templateStore.length > 0) {
        //Load Template Items
        $("#templateLoading").html("Template store contains: " + 
			templateStore.length + " item(s).");
        
        //get the filter mode
        var filter = $("#selectFilter option:selected").val();

        //Get each items Title and add it to the list subject to filters
        for (item in templateStore) {
            var addit = false;
            switch (filter) {
                case "All":
                    //Add all items
                    addit = true;
                    break;

                case "Auto":
                    //Always add the snippets regardless of Answer/Message
                    if (templateStore[item].Type == "Snippet") {
                        addit = true;
                    }
                    else {
                        //if the item matches the page (message/answer) then addit
                        addit = (templateStore[item].Type == urlType);
                    }
                    break;

                default:
                    //Add if item == filter type
                    addit = (templateStore[item].Type == filter); 
            }

            if (addit) {
                $("#selectTemplates").html($("#selectTemplates").html() + 
		"<option value="\"" + item.toString() + "\">" + 
		templateStore[item].Title + "</option>");
            }
        }
        $("#templateLoading").html($("#templateLoading").html() + 
	" Displaying: " + $("#selectTemplates").prop("length").toString() + " item(s)");
        $("#templateListing").show();
    }
    else {
        // No items to Load
        $("#templateLoading").html("No items located in local store.");
        $("#templateListing").show();

        if (!templateSelectMode) {
            $("#templateEdit").hide();
            $("#buttonEdit").hide();
            $("#buttonClone").hide();
            $("#buttonDelete").hide();
        }
        else {
            $("#buttonSelect").hide();
        }
    }
    
    //Add always is shown (in editor form).
    if (!templateSelectMode) {
        $("#buttonAdd").show(); 
    }
}

Adding and Edit Template Items

When the user clicks Add, the editor form is displayed where the user can enter the text for the various properties. A new template object is then created and pushed into the array and stored out to local storage.

If the user selects an existing item, this is loaded into the editor, can be updated by the user and pushed back into the array, and written out to the local storage. The edit form is just a hidden DIV which is swapped out with the template selector DIV. This edit form is shown below:

CPTemplatesChromeExt/editor_page.png

 

The code below demonstrates how the item selected in the list for edit is identified, pulled from the storage array and the form updated with the existing details:

JavaScript
function buttonEdit_Click() {
    templateEditMode = true;
    templateEditModeItem = parseInt($("#selectTemplates option:selected").val());

    //V1.1 Change Switch block replaces If and added Snippet handling
    switch (templateStore[templateEditModeItem].Type) {
        case "Message":
            $("#RadioTypeMessage").prop('checked', true);
            break;

        case "Answer":
            $("#RadioTypeAnswer").prop('checked', true);
            break;

        case "Snippet":
            $("#RadioTypeSnippet").prop('checked', true);
            break;
    }

    $("#TextTitle").val(templateStore[templateEditModeItem].Title);
    $("#TextSubject").val(templateStore[templateEditModeItem].Subject);
    $("#TextBody").val(templateStore[templateEditModeItem].Body);

    $("#templateListing").hide();
    $("#templateEdit").show();
}

The code below is the save operation performed, for both adding a new item or editing an existing item. When an item is being edited, a new item is created and the old item is replaced in the storage array. This updated array is then written out to local storage:

JavaScript
function buttonSave_Click() {
    //Create new item
    var item = new Template();

    //Add item properties
    item.Type = $("input[name=RadioType]:checked").val();
    item.Title = $("#TextTitle").val().toString();
    item.Subject = $("#TextSubject").val().toString();
    item.Body = $("#TextBody").val().toString();

    if (templateEditMode) {
        //Edit Mode
        templateStore[templateEditModeItem] = item;
        templateEditMode = false;
    }
    else {
        //Add Mode
        templateStore.push(item);
    }

    //Save to local storage
    saveToLocalStorage();

    //Restart
    initEditorView();
}

As you can see in the code above, we create a new template by executing new Template(). Template is actually a function which returns a suitable formed object. The function that performs this is:

JavaScript
//Template Object Constructor
function Template() {
    this.Type = "";
    this.Title = "";
    this.Subject = "";
    this.Body = "";
}

You may have also noticed on the editor selector form, a Clone button. This allows us to duplicate an existing item. To do this, we use JQuery to perform a deep copy of the existing object into a new object, first we get the existing item from the array, clone it to a new object, add the new object to the array and write the update out to the local storage. The code is shown below:

JavaScript
function buttonClone_Click() {
    //Get the current selected item
    var original = templateStore[parseInt($("#selectTemplates option:selected").val())];
    
    //Perform a deep copy of the original using JQuery
    var copy = $.extend(true, {}, original);

    //Push it into the store at the end
    templateStore.push(copy);

    //save
    saveToLocalStorage();

    initEditorView();
}

Filtering Capabilities

Version 1.1 introduced filtering capabilities on the Editor Form and the Selector Form. This allows the user to filter down to work with items if they have many of the three different types (Message/Answer/Snippet).

The selector form also has an AUTO filter (default selection). What this does is checks to see which CodeProject page the user is on, and filters the lists accordingly. If the user is on the Message editor, the list is automatically filtered for Messages and Snippets. If the user is on a Question Page, then the list is filtered automatically for Answers and Snippets.

Filtering is achieved by the use of storing the template array item index within the HTML markup as the value property of the options, and only adding the relevant items to the list, the value is then read to identify the source index within the storage array.

What Else Do I Need to Know?

The Title field is your own freetext for identifying your template item and what it contains, the Subject field is only used by Forum Messages, the Body is used by Forum Messages, Answers and Snippets.

Any More? What's Next?

Refer to the previous extension link for details on packaging, deployment and hosting, etc.

The links at the top of the article are the unpackaged source and also a link to the packaged distribution server. If you use this, any future updates will automatically be picked up by Chrome.

The Future

If there is anything you can think of, any ideas you have, or if you find problems, then leave a message below, and I will see what I can do.

Well, that is it for the time being, and hope you find this of use.

Reference Links

History

  • 29th August 2014 - Update to V2 to support new Google policy requirements.
  • 23rd October 2013 - Added note regarding install from outwith Chrome Web Store 
  • 16th September 2011 - V1.1, Added snippet type, filtering capability and fixed Add Item bug
  • 22nd August 2011 - V1.0, Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
Scotland Scotland
I have been working in the Oil & Gas Industry for over 30 years now.

Core Discipline is Instrumentation and Control Systems.

Completed Bsc Honours Degree (B29 in Computing) with the Open University in 2012.

Currently, Offshore Installation Manager in the Al Shaheen oil field, which is located off the coast of Qatar. Prior to this, 25 years of North Sea Oil & Gas experience.

Comments and Discussions

 
GeneralGood One!! Pin
deepakaitr1234519-Aug-15 19:57
deepakaitr1234519-Aug-15 19:57 
GeneralMy vote of 5 Pin
ThatsAlok23-Oct-13 20:46
ThatsAlok23-Oct-13 20:46 
GeneralMy vote of 5 Pin
Marco Bertschi14-May-13 22:17
protectorMarco Bertschi14-May-13 22:17 
GeneralRe: My vote of 5 Pin
DaveAuld16-May-13 21:36
professionalDaveAuld16-May-13 21:36 
GeneralRe: My vote of 5 Pin
Marco Bertschi16-May-13 21:49
protectorMarco Bertschi16-May-13 21:49 
Cheers, too!

Your "Extension Installation Link - Installation served from author's website" linky is broken (top most link of the article).
Marco Bertschi

Twitter | Articles | G+

GeneralRe: My vote of 5 Pin
DaveAuld16-May-13 22:40
professionalDaveAuld16-May-13 22:40 
GeneralRe: My vote of 5 Pin
DaveAuld18-May-13 5:56
professionalDaveAuld18-May-13 5:56 
QuestionWow ! how did I miss this gem ? +5 Pin
BillWoodruff6-Sep-12 1:14
professionalBillWoodruff6-Sep-12 1:14 
AnswerRe: Wow ! how did I miss this gem ? +5 Pin
DaveAuld6-Sep-12 1:19
professionalDaveAuld6-Sep-12 1:19 
GeneralGreat stuff - definite 5 Pin
Andrei Straut5-Sep-12 8:49
Andrei Straut5-Sep-12 8:49 
QuestionExcellent article Pin
Marcelo Ricardo de Oliveira29-Aug-11 16:06
Marcelo Ricardo de Oliveira29-Aug-11 16:06 
AnswerRe: Excellent article [modified] Pin
DaveAuld29-Aug-11 20:39
professionalDaveAuld29-Aug-11 20:39 
GeneralRe: Excellent article Pin
Marcelo Ricardo de Oliveira30-Aug-11 15:09
Marcelo Ricardo de Oliveira30-Aug-11 15:09 
BugLocalStorage error [modified] Pin
DaveAuld27-Aug-11 6:30
professionalDaveAuld27-Aug-11 6:30 
QuestionPretty neat stuff. Pin
Sacha Barber24-Aug-11 2:38
Sacha Barber24-Aug-11 2:38 
AnswerRe: Pretty neat stuff. Pin
DaveAuld24-Aug-11 3:01
professionalDaveAuld24-Aug-11 3:01 
GeneralRe: Pretty neat stuff. Pin
Sacha Barber24-Aug-11 3:40
Sacha Barber24-Aug-11 3:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.