Click here to Skip to main content
Click here to Skip to main content

Google Chrome Extension - CodeProject Template Items

, 23 Oct 2013
Rate this:
Please Sign up or sign in to vote.
Google Chrome Extension that provides Message and Answer templating

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:

{
  "name": "CodeProject Template Items",
  "version": "1.1",
  "description": "Chrome Extension to provide CodeProject template items.",
  "icons":{"48":"images/bob48.png", "128":"images/bob128.png"},	//Define any icon sizes 
			//and the files that you want to use with them. 48/128 etc.
  "background_page": "cptemplates.html",
  "page_action": {
    "default_icon": "images/bob.png",               // image to show in the address bar
    "default_title": "CodeProject: Insert Template Items", // text to show in the tooltip
    "default_popup": "selector.html"                       // template selector page
  },
  "options_page": "editor.html",	// Page to handle the options for the extension, 
				// used to edit the templates
  "permissions": [
    "tabs", "http://*.codeproject.com/"             
  ]
  "update_url": 
   "http://www.dave-auld.net/ChromeExtensions/CPTemplatesChromeExt/updates.xml"// Auto 
								//Update location
}

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

  • background_page - This is the file that runs in the background of the browser, and sets up the browser integration of the extension.
  • 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.

Background Page - What is it Doing?

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 
	Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
    <title>CodeProject Template Items - Background Page</title>
        <script type="text/javascript">

            // Called when the url of a tab changes.
            function checkForTargetMessageEditorUrl(tabId, changeInfo, tab) {
                // 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);            
    </script>
</head>

<body bgcolor="#ffcc66">
    <p>CodeProject Template Items Background Page Process - 
		Nothing to see, move along please.</p>
</body>
</html>

Pretty standard HTML page, with integrated script. When the page 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:

//-----------------------------------------
//     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.

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:

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:

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.

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:

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:

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:

//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:

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

  • 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)

About the Author

DaveAuld
Engineer
Scotland Scotland
I have been working in the Oil & Gas Industry for over 20 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 for the Beryl Bravo platform, which is located ~180 miles NE of Aberdeen, Scotland in the Northern North Sea.
Formely on the Forties Charlie platform, which is located ~110Miles NE of Aberdeen.
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
GeneralMy vote of 5 PinmemberThatsAlok23-Oct-13 20:46 
GeneralMy vote of 5 PinprofessionalMarco Bertschi14-May-13 22:17 
Really really great! How did I miss it for so long?
 
You did a great job!
GeneralRe: My vote of 5 PinprotectorDaveAuld16-May-13 21:36 
GeneralRe: My vote of 5 PinprofessionalMarco Bertschi16-May-13 21:49 
GeneralRe: My vote of 5 PinprotectorDaveAuld16-May-13 22:40 
GeneralRe: My vote of 5 PinprotectorDaveAuld18-May-13 5:56 
QuestionWow ! how did I miss this gem ? +5 [modified] PinmemberBillWoodruff6-Sep-12 1:14 
AnswerRe: Wow ! how did I miss this gem ? +5 PinmentorDaveAuld6-Sep-12 1:19 
GeneralGreat stuff - definite 5 PinmemberAndrei Straut5-Sep-12 8:49 
QuestionExcellent article PinmvpMarcelo Ricardo de Oliveira29-Aug-11 16:06 
AnswerRe: Excellent article [modified] PinmentorDaveAuld29-Aug-11 20:39 
GeneralRe: Excellent article PinmvpMarcelo Ricardo de Oliveira30-Aug-11 15:09 
BugLocalStorage error [modified] PinmentorDaveAuld27-Aug-11 6:30 
QuestionPretty neat stuff. PinmvpSacha Barber24-Aug-11 2:38 
AnswerRe: Pretty neat stuff. PinmentorDaveAuld24-Aug-11 3:01 
GeneralRe: Pretty neat stuff. PinmvpSacha Barber24-Aug-11 3:40 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 23 Oct 2013
Article Copyright 2011 by DaveAuld
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid