This is a follow up post on my previous post about a tree-view item picker. Right now, this Tridion GUI extension works well for rich text fields : when you click a button in the ribbon toolbar, you get a popup with a number of keywords, and when the user selects a keyword, some property of that keyword is inserted in the rich text field.
Now, the same functionality had to be achieved for regular text fields. Tridion has a lot functionality available for dealing with RTF fields, but extending this to regular text fields requires some extra custom coding
There are two main problems : finding out which text field was active when you clicked the button, and inserting the content in the text field on the cursor position.
Finding out about the active textfield
Any button acting on a rich text field has some properties that hold the active text area (using the code “target.editor”). Unfortuntately, an event triggered by a normal textfield also has this target property, but that only holds information about the currently selected component, so target.editor is null. Also, the fieldbuilder gives no help. The code “$display.getView().properties.activeEditor” does not work, since by the time you click the button, the textfield has already lost focus, so activeEditor is null.
So, we need to add some custom plumbing. Fortunately, we can add some custom events on focus and on blur, to store everything we need
var activeFieldName = ""; var activeElement = ""; var cursorPosition = 0; var currValue = ""; function StoreFocusedElementName(elem) { var disp = $display.getView(); activeFieldName = disp.properties.controls.fieldBuilder.properties.focusField.getFieldName(); activeElement = elem; cursorPosition = 0; currValue = ""; } function StoreFocusedElementData(elem) { activeElement = elem; cursorPosition = getCaretPosition(elem); currValue = elem.value; } /* ** Returns the caret (cursor) position of the specified text field. ** Return value range is 0-oField.value.length. */ function getCaretPosition(oField) { // Initialize var iCaretPos = 0; // IE Support if (document.selection) { // Set focus on the element oField.focus(); // To get cursor position, get empty selection range var oSel = document.selection.createRange(); // Move selection start to 0 position oSel.moveStart('character', -oField.value.length); // The caret position is selection length iCaretPos = oSel.text.length; } // Firefox support else if (oField.selectionStart || oField.selectionStart == '0') iCaretPos = oField.selectionStart; // Return results return (iCaretPos); } function setPopupHandlers() { var textfields = $$("#SchemaBasedFields .field input.text"); var textareas = $$("#SchemaBasedFields .field textarea"); var textfieldsmeta = $$("#ItemMetadata .field input.text"); var textareasmeta = $$("#ItemMetadata .field textarea"); for (i = 0; i < textfields.length; i++) { //alert(textfields[i]); textfields[i].onfocus = function () { StoreFocusedElementName(this) }; textfields[i].onblur = function () { StoreFocusedElementData(this) }; } for (i = 0; i < textareas.length; i++) { textareas[i].onfocus = function () { StoreFocusedElementName(this) }; textareas[i].onblur = function () { StoreFocusedElementData(this) }; } for (i = 0; i < textfieldsmeta.length; i++) { //alert(textfields[i]); textfieldsmeta[i].onfocus = function () { StoreFocusedElementName(this) }; textfieldsmeta[i].onblur = function () { StoreFocusedElementData(this) }; } for (i = 0; i < textareasmeta.length; i++) { textareasmeta[i].onfocus = function () { StoreFocusedElementName(this) }; textareasmeta[i].onblur = function () { StoreFocusedElementData(this) }; } }
The code does the following:
when the GUI extension is loaded, we call the function setPopupHandlers(). This selects all the different textboxes (single line and multi-line textfields in the fields and in the metadatafields), and attaches two events to each of them : an onfocus event, and an onblur event.
The onfocus event stores the active field name, and resets some other values.
The onblur event stores the current cursor position and also the value of the textfield (we need this info later.
This solves the first part of our problem : in our gui extension execute event, we now know about which field is active when we triggered our custom code.
Inserting the selected value in the textfield
The second issue is inserting the content in the textfield. In our RTF Field, we have a standard function “target.editor.applyHtml(‘html-code’) which does all this for you.
If we want to achieve similar behaviour for our textfield, we have to write some more javascript code.
$evt.addEventHandler(popup, "select", function TagSelectorBtn$execute$onPopupSubmitted(event) { var selectedKeyword = event.data.itemId; if (selectedKeyword) { TagSelectorTextkeyword = $models.getItem(selectedKeyword); if (TagSelectorTextkeyword.isLoaded()) { TagSelectorBtn$execute$setKeyword(TagSelectorTextkeyword.getKey()); } else { $evt.addEventHandler(TagSelectorTextkeyword, "load", this.getDelegate(TagSelectorBtn$execute$onselectedKyLoaded)); TagSelectorTextkeyword.load(); } } else { alert("event data = " + event.data); } // close the active pop up window TagSelectorTextpopup.close(); }); //Handle the CTA styles popup cancel event function TagSelectorBtn$execute$onPopupCanceled(event) { TagSelectorTextpopup.close(); }; function TagSelectorBtn$execute$onselectedKyLoaded(event) { var selectedDescription = TagSelectorTextkeyword.getKey(); TagSelectorBtn$execute$setKeyword(selectedDescription); }; function TagSelectorBtn$execute$setKeyword(selectedDescription) { var begincontent = currValue.substr(0, cursorPosition) + selectedDescription; var content = begincontent + currValue.substr(cursorPosition); activeElement.value = content; activeElement.focus(); var pos = begincontent.length; //activeElement.setSelectionRange(cursorPosition + customValue.length-1); activeElement.setSelectionRange(cursorPosition, pos); };
This piece of code acts on the “select” event of our tree-view item picker (see my previous post for more info on this). In the select event, we receive the tcm id of the selected object, so we can retrieve it from the content manager.
The part that’s different from the RTF field version is this
function TagSelectorBtn$execute$setKeyword(selectedDescription) { var begincontent = currValue.substr(0, cursorPosition) + selectedDescription; var content = begincontent + currValue.substr(cursorPosition); activeElement.value = content; activeElement.focus(); var pos = begincontent.length; activeElement.setSelectionRange(cursorPosition, pos); };
First, we split the content of the textbox on the cursor position. We add the text to insert to the first part of the original value, and then we append the rest of the original value.
Then we set the value of the activeelement to the new content.
And at last, we have to set the focus back to that element, and also set the cursor at the position after the point where we inserted the text (in this example, we select the inserted piece of text, so it’s clear to the editors what got inserted.
Some notes
I believe you might be able to achieve similar functionality using a custom link on your textfield. However, this would require you to add this custom link in each text field in all of your schema’s.
There might be different (better) ways to do this, maybe there is some function available in the Anguilla framework that I don’t know about. If you have anything that might improve this code, feel free to add a comment.
Good article Harald….. I have not worked on GUI extensions earlier and planning to use it.
We have a requirement were editors want to select a page in a component field. (This will be used for internal page linking rather than resolving by component links).
So far I managed to get TCM ID using Item Selector tool.
What I want to check further is, ability for editor to open the linked page from component field. (Like the way we open linked components in a component).