Authored by: Igor Polyakov, Senior Principal Product Manager, Oracle Oracle Sites Cloud Service: Getting Started with Custom Components In this tutorial you will learn how to create a simple Sites Cloud Service (SCS) component that uses a

element with the “contenteditable" attribute enabled to allow end users to enter their own text in the page that will be saved with the component. It shows you how to convert the pre-defined files you get when you create a component into your own implementation. The intent is to describe what is required and what is optional within those seeded files. When you create a component you get a set of seeded files that will work out of the box. The seed code covers most of the functionality of a component within the product and the "Tutorial: Developing Components with " section in the SCS documentation explains how all the pieces of the components fit together. In this tutorial, we cover how to change the seeded code to create your own component. It also covers other aspects not covered in the standard tutorial such as: • How to provide different templating based on the viewMode of the component • Saving data from the component instead of from the settings panel • Integration with the page's undo/redo events Note: This sample is simply aimed at explaining the various pieces that make up a generic SCS Custom Component. It should not be used "as is" as a production component.

Step 1: Create New Component After this step you will have created your component with the Sites Cloud Service that you can immediately drop onto the page. This is the starting point for creating any new component. To create a local component: 1. Navigate to Sites -> Components 2. Select option Create -> Create Local Component 3. Enter a name, for example “BasicTextEditor” and optionally, description 4. Click "Create" to create new component

Checkpoint 1 Now that you have successfully created a component, you should see this component in the Component catalog as well as in the Add > Custom component palette for any site you create. Use the following steps to validate your component creation: 1. Create a new site using any seeded Template, for example create a site called “ComponentTest” using the “StarterTemplate" template. 2. Select Edit option and create an update for the site to open it in the Site Builder 3. Edit a page within the site that you created 4. Click on the Add ("+") button on the left bar and select "Custom" for the list of custom components 5. Select the "BasicTextEditor" from the custom component Palette and drop it onto the page. You should now see a default rendering for the local component you created

6. Select the context menu in the banner for the component you dropped 7. Select "Settings" from the drop-down menu. You can change setting to see how seeded component rendering will change. The following steps cover how you can modify seeded files to create a new custom component and how to modify it for your own purposes.

Step 2: Create the Basic Text Component In this step you will remove most of the content in seeded files to create a simple text component. It will simply display the text that you seed when you create the viewModel. In subsequent steps you will learn how to make that text editable by the end users. To review the structure of your local component: 1. Using the Document Cloud Service Desktop Sync Client, locate your component and sync it with the file system (select the Start Sync option) • If you don't have the Desktop Sync Client, you can also select the component in the Components tab of the Sites Cloud Service and drill down to see the files 2. If you list the files under the component, you will see: • “assets” folder with the component files: o render.js o settings.html • appinfo. – JSON file with component description , and • _folder_icon.jpg – icon that is displayed in the Components catalog For details about appinfo.json file content see “About Developing Component” section in documentation. To create a simple text component: 1. Open up the render.js file under “assets” directory in your favorite text editor 2. Change the sampleComponentTemplate object to: // ------// Define a Knockout Template for your component // ------var sampleComponentTemplate = '' + '

' + '
' + '
' + '';

3. Change the SampleComponentViewModel object to: // ------// Define a Knockout ViewModel for your template // ------var SampleComponentViewModel = function (args) { var self = this, SitesSDK = args.SitesSDK;

// store standard args self.mode = args.viewMode; self.id = args.id;

// create observables to persiste the data self.text = ko.observable('Welcome to Components World!');

// handle initialization self.customDataInitialized = ko.observable(false); self.initialized = ko.computed(function () { var initialized = self.customDataInitialized(); return initialized; }, self);

// handle property changes self.updateCustomSettingsData = function(customSettingsData) { if (customSettingsData && customSettingsData.text) { self.text(customSettingsData.text); } self.customDataInitialized(true); };

// initialize the viewModel // // get the Custom Settings Data, we need both before first render SitesSDK.getProperty('customSettingsData', self.updateCustomSettingsData); };

This update changes the component to the core necessary pieces:

o ViewModel: o Storage of the passed in Sites SDK & instance values for the component: SitesSDK, self.id and self.mode o A single knockout observable to display the text: self.text o Generic code to retrieve the customSettingsData via the SDK and note component has been initialized once that has happened o Template: o A minimal template displaying the text once the component initialization has completed Checkpoint 2 1. Sync your changes 2. In the Site Builder refresh the page, for example by switching between Preview and Edit modes. At this point you should see “Welcome to Components World!” text displayed in the component:

Step 3: Separate Knockout Template to HTML File So far your component was using Knockout template that is defined inline in the render.js file. In this step you will learn how to use Knockout template that is defined in a separate HTML file which makes it easier for you to change component view. 1. In the “assets” directory, create new HTML file template.html file 2. Move HTML defined in the sampleComponentTemlate object to this file:

3. Delete explicit definition of the sampleComponentTemlate object and change RequireJS module definition in the render.js to: /* globals define */ define(['knockout', '', 'text!./template.html'], function (ko, $, sampleComponentTemplate) { 'use strict'; The above define(…) call initializes sampleComponentTemplate object with the content of the template.html file. Checkpoint 3 1. Sync your changes 2. In the Site Builder refresh the page, for example by switching between Preview and Edit modes. At this point you should see the same “Welcome to Components World!” text displayed in the component.

Step 4: Enable In-place Text Edit The purpose of this component is to allow the end user to edit the text directly in the page. To do this we will leverage the "contenteditable" attribute against a

. After this step, the user will be able to click on the text and modify it. To make the component editable, make the following changes: 1. In template.html, change
element as follows:
The “user-select” property in CSS controls how the text in an element is allowed to be selected. For cross-browser compatibility you may need to use (for details see): -webkit-user-select: text; -khtml-user-select: text; -moz-user-select: text; -o-user-select: text; user-select: text;

2. Then in render.js, change the SampleComponentViewModel object: o Replace: // create observables to persiste the data self.text = ko.observable('Welcome to Components World!');

o With: // create observables to persiste the data self.text = ko.observable('Welcome to Components World!');

// set the default value for text self.editText = ko.observable('Edit me');

The reason we want to have an "editText" observable as well is that we want to have some indication to the end user that they can edit the value of the text but we don't want to store that value. We'll look at storage in a later step and how to pass the values between these observables. Checkpoint 4 1. Sync your changes 2. Take your page into Edit mode 3. Click on the “Edit me” text 4. Make Changes to the text and click out After this, you should see the text updated while you are in Edit mode. Also note the following:

o If you switch to Preview mode, your changes are lost and text reverts to “Edit me” o While in Preview mode, you can again click on the “Edit me” text and change it.

Step 5: Handle the Site Builder Edit/Preview Modes In this step you will ensure that the

element’s content is not editable during preview or at runtime. To do this you need to have the component template display different content based on the mode passed in to the component. In template.html, make the following change to the HTML:

Then in render.js, update the SampleComponentViewModel object to create the "editMode" observable:

o Change: // store standard args self.mode = args.viewMode; self.id = args.id;

o To: // store standard args self.mode = args.viewMode; self.id = args.id;

// note if in Edit mode self.editMode = self.mode === 'edit';

Now when the mode is passed in, it will either display "edit me" when editing or "a text component" when previewing since we haven't saved and synced the editing changes. Checkpoint 5 1. Sync your changes 2. Take the page into Edit mode. In Edit mode you will see the “Edit me” text. 3. Click on “Edit me” and validate that you can update the text. 4. Take the page into Preview mode. a. In Preview mode (or at runtime if you publish the site) your will see “Welcome to Components World!” text displayed b. You also can no longer edit the text in Preview mode.

Step 6: Persist Text Edit Changes in this step, you will persist the changes the end user makes and be able to see those changes copied across to be visible when you preview the component. 1. At the top of render.js, create a custom binding handler to handle the focus and blur events from the contenteditable

element: a. Change: 'use strict';

b. To 'use strict';

// support in-place editing of text ko.bindingHandlers.myCompSetup = { init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { $(element).on('blur', function(event) { viewModel.handleBlur(event); }); $(element).on('focus', function(event) { viewModel.handleFocus(event); }); } }; 2. In template.html, update the contenteditable

element a. Change:
'

b. To:

3. In render.js, replace the SampleComponentViewModel object with the implementation of the blur/ focus events required by the custom binding handler and persist the change: // ------// Define a Knockout ViewModel for your template // ------var SampleComponentViewModel = function (args) { var self = this, SitesSDK = args.SitesSDK, nbspChar = String.fromCharCode(65279);

// store standard args self.mode = args.viewMode; self.id = args.id;

// note if in Edit mode self.editMode = self.mode === 'edit';

// create observables to persiste the data self.text = ko.observable('');

// set the default value for text self.defaultText = 'Edit me'; self.editText = ko.observable(''); self.editingText = ko.observable(false);

// support in-place editing the text self.handleFocus = function(event) { // workaround Firefox issue with parent draggable - turn draggable back off SCSRenderAPI.setComponentDraggable(self.id, false);

// set the user text to the actual text value self.editText(self.text() || nbspChar);

// started text edit self.editingText(true); };

self.handleBlur = function(event) { var $span = $(event.target), userInput = $span.html();

// workaround Firefox issue with parent draggable - turn draggable back on SCSRenderAPI.setComponentDraggable(self.id, true);

// copy the final text value into the text value if (!userInput || userInput === nbspChar) { self.text("");

// make sure notified that value has changed self.text.valueHasMutated(); } else { // update with the new value self.text(userInput); }

// completed text edit self.editingText(false); };

// save changes for text self.text.subscribe(function(val) { if (self.saveContent) { SitesSDK.setProperty('customSettingsData', { 'text' : val } ); } });

// handle initialization self.customDataInitialized = ko.observable(false); self.initialized = ko.computed(function () { var initialized = self.customDataInitialized(); if (initialized) { // only writeback content after initialization self.saveContent = true; } return initialized; }, self);

// handle property changes self.updateCustomSettingsData = function(customSettingsData) { if (customSettingsData && customSettingsData.text) { self.text(customSettingsData.text); } self.editText(self.text() ? self.text() : self.defaultText);

self.customDataInitialized(true); };

// initialize the viewModel // // get the Custom Settings Data, we need both before first render SitesSDK.getProperty('customSettingsData', self.updateCustomSettingsData); };

The way this code works is that:

o Whenever the blur event occurs, the self.handleBlur() function writes changes to the text through to the self.text observable. o Whenever the self.text observable changes, the self.text.subscribe() function sets the "customSettingsData" to the current value of self.text(), which the SDK then saves to the page data o We also don't want to write the initial value that is retrieved for self.text() on initialization so we only write back to "customSettingsData" when we want to save the content (self.saveContent is set to true) o Finally, we want to have some "placeholder" text so we have introduced the "defaultText", which disappears when the user clicks on editText and there is nothing yet stored Checkpoint 6 You can now edit the text in Edit mode and have the changes display in Preview mode. The changes are now saved as part of the page data. 1. Sync your changes 2. Refresh site page in the Builder to pick up changes to the component 3. Take the page into Edit mode 4. Click on the "Edit me" text displayed in the component and make a change 5. Click out of the text and then switch to Preview mode 6. At this point you will see your changes persisted 7. Take the page back into edit mode. Again, you will see the text you previously entered

Step 7: Integration with Site Builder Undo / Redo In the Site Builder, as you made change to the text and clicked outside, you may have noticed the "Undo/ Redo" icons at the top of the page become enabled. This is because you are saving information to your component, which in turn stores that information in the page data. However, if you clicked undo, you would see no change to your component until you went all the way to removing the component from the page. The reason for this is that you don't have any code to handle the Undo/ Redo events within the viewModel. Whenever an Undo/ Redo event occurs that affects your custom component, the "updateSettings" event is called with the current data for the component. The component should re-render with this data to reflect the current state of the component on the page. To update your component to take part in the page's Undo/ Redo events, do the following to support listening for updates to the "customSettingsData":

o Change the SampleComponentViewModel to include: // listen for settings update self.updateSettings = function(settings) { if (settings.property === 'customSettingsData') { // turn off saving content self.saveContent = false;

// update the content self.updateCustomSettingsData(settings.value);

// turn on saving content self.saveContent = true; } }; SitesSDK.subscribe(SitesSDK.MESSAGE_TYPES.SETTINGS_UPDATED, self.updateSettings);

// initialize the viewModel // // get the Custom Settings Data, we need both before first render SitesSDK.getProperty('customSettingsData', self.updateCustomSettingsData);

Now, whenever the underlying settings value changes, the component will receive the event and update the text. Checkpoint 7 1. Sync the file 2. Take the page into Edit mode 3. Click on the text and change it to "one" 4. Click out of the text 5. Click on the text and change it to "two" 6. Click out of the text 7. Click on the text and change it to "three" 8. Click out of the text 9. Now click "Undo" a. The text will update to "two" 10. Click "Undo" again a. The text will update to "one" 11. Click "Undo" again a. The text will update to the default value 12. Clicking "Redo" does the opposite as expected

Step 8: Review You now have a very Basic Text Editor component. For a fuller editor features, you can integrate a third party library to include a toolbar such as CKEditor, which is already part of the SCS code stack or, if you are using a Bootstrap theme, Tiny Editor.

Step 9: Suggested Further Steps. As an exercise you can further enhance Basic Text Editor component: 1. Add protection against cross-site scripting attacks a. Strip out any un-allowed tags in the HTML that was entered by the user before the HTML is rendered into the page b. This is not specific to custom components but you should always make sure you encode any user entered input before you display it to avoid XSS attacks 2. Add Styles a. Update the settings.html to handle an LOV of styles to select (

,

,

,

) and wrap the rendered content with the selected style 3. Add in a 3rd party editing toolbar a. Integrate with Tiny Editor or CKEditor 4. Add Link with Triggers a. Set the link around all the text and set from the settings panel or allow the user to select some text and raise a trigger from it 5. Implement an Action to change the text a. Add in an action that will set the text value to that passed in