Interactive Resource Editing - RickStrahl/Westwind.Globalization GitHub Wiki

Westwind.Globalization supports resource editing on source HTML pages allowing optional links that point back directly to the referenced resources in the Web Resource Editor. Resource icons are added to all HTML elements that include a data-resource-id element and a data-resource-set attribute on any parent element, which in turn highlights the specified element with a clickable icon. Click the icon and you're whisked over to Web Resource Editor with the resource selected for editing.

Resource Edit View adds clickable icons

The form above demonstrates the process. You enable resource editing on a form using either your own code, or our helper button/icon that you can add to any page. Once resource editing is enabled the little resource icon pop up next to any element that has a data-resource-id attribute associated with it. When one of the icons is clicked, the resource editor form is popped up with the relevant resource pre-selected. If the topic doesn't exist a new resource form lets you create new resource on the spot.

Note: Resource editing can also be extremely useful for CMS like features where content can be dynamically edited and updated, especially when coupled with the MarkDown support in this library.

Usage

Resource editing is enabled with the following steps:

  • Add the required JavaScript references to your page
  • Add CSS to provide the Resource Link display
  • Enable Resource Editing by calling showResourceIcons()
  • Mark up elements to edit with data-resource-id and data-resource-set

The idea is that you can markup any elements you want to edit with the data-resource-id attribute. The showResourceIcons() function then finds each of these marked up elements and injects clickable resource icons, using JavaScript code. The entire process is controlled from the client side, so it works both in ASP.NET Pages/Views, in plain HTML pages or pure client side SPA style applications.

Add Scripts to your Page

First you need to add jQuery and the wwResourceEditor.js scripts to any page where you want to edit resources (or in a Layout/Master page if you want this on all pages):

<script src="LocalizationAdmin/bower_components/jquery/dist/jquery.min.js"></script>

Then add the actual ww.resourceEditor.js script:

<script src="localizationAdmin/scripts/ww.resourceEditor.js"></script>

Add Resource Link CSS

Resource links depend on CSS to render the highlight icons. The icons are rendered on top of existing controls using absolute positioning. By default a FontAwesome flag symbol is used for the icon, but you can change that to any character by modifying the CSS (see comments in CSS). The CSS also contains styling for an optional resource edit button that can be embedded into the page to toggle the resource edit mode.

<!-- optional - if you don't use change the CSS to display plain character -->
<link href="./LocalizationAdmin/bower_components/font-awesome/css/font-awesome.min.css"
      rel="stylesheet"/>

<style>            
.resource-editor-icon, .resource-editor-icon:hover,  .resource-editor-icon:visited {
    position: absolute;
    display: inline;
    height: 13px;
    width: 13px;     
    text-decoration: none;
    z-Index: 999999;                 
    margin: -14px 0 0 -2px;
    cursor: pointer; 
    opacity: 0.45;                                   
}
.resource-editor-icon:hover {
    opacity: 1;         
}
.resource-editor-icon:before {
    font-family: fontawesome;
    content: "\f024";   /*flag*/ 
    /*font-family: Arial;
    content: "#";*/
    font-size: 9pt;              
    color: red;                      
}

/* optional edit button/icon on bottom right of page */
.resource-editor-button {   
    z-index: 999999;
    color: white;        
    background-color: DarkGreen;
    opacity: 0.35;     
    position: fixed;
    bottom: 10px;
    right: 20px;
    padding: 7px 9px 5px 10px;        
    border-radius: 50%;
}
.resource-editor-button.off {
    background-color: #b64545;
}
.resource-editor-button:before{
    font-family: fontawesome;
    content: "\f024"; /* flag */
    font-size: 14pt;
}
.resource-editor-button:hover {
    opacity: 0.65;
}
</style>

You can add these style to individual pages or to a global style sheet.

Activate or Deactivate Resource Editing

To activate resource editing call the showResourceIcons() function along with any options (all are optional):

ww.resourceEditor.showResourceIcons({ 
   adminUrl: "./LocalizationAdmin/",  // default
   // this causes a popup window. Use "" to show as tabbed view
   editorWindowOpenOptions: "height=600, width=900, left=30, top=30",
   editorWindowName: "_localization-resource-editor" // default
});

To turn off resource editing:

ww.resourceEditor.removeResourceIcons();

A common way you handle activation is based on a toggle button which looks like this:

<script>
    var toggleResourceEditMode = false;
    $("#btnEditResources").click(function() {
        toggleEditMode = !toggleResourceEditMode; 
        if(toggleResourceEditMode)
            ww.resourceEditor.showResourceIcons({ 
                adminUrl: "./localizationAdmin/" 
                editorWindowOpenOptions: "height=600, width=900, left=30, top=30",
            });
        else
            ww.resourceEditor.removeResourceIcons();
    });
</script>

Resource Editing Toggle Button

There's also a custom resource editor icon that you can add to any page that can provide automatic toggling of the resource edit mode. It looks like this:

To use this icon in your page simply use:

<script>
    ww.resourceEditor.showEditButton({
        adminUrl: "./localizationAdmin/"
    });
</script>

The same options as showResourceIcons() can be passed as parameter.

Mark up Editable Resource Elements

In order for elements in your Web Page to be linked for resource editing you can mark them up with two supported attributes:

  • data-resource-id
    The resource id to link to. This attribute needs to be attached to any element or container of an element that is associated with the resource. Doesn't have to be on the actual localized element - use any element that logically makes sense for editing. Required.

  • data-resource-set
    The ResourceSet that that the resource belongs to. This can be declared on the same element as the ResourceId or anywhere in the parent element hierarchy. The library searches up the tree to find the nearest data-resource-set attribute and uses its value.

In practice you typically declare data-resource-set at the <body> or some other 'view-level' element, and data-resource-id on each element to display the icon on. You only override data-resource-set in specific cases where you are referencing an non-local resource set.

Here's what this might look like in an ASP.NET MVC based view using both DbRes.T() or strongly typed resources if you've exported them:

<body data-resource-set="LocalizationForm">

   <div class="page-title" 
        data-resource-id="PageTitle">
     @DbRes.T("PageTitle","LocalizationForm")
   </div>

   <-- if you have non-wrapped text you can add a <span> tag -->
   <span data-resource-id="HelloWorld">@LocalizationForm.HelloWorld</span>
</body> 

Clicking on any of these resource links causes a lookup for the LocalizationForm resource set and the PageTitle and HelloWorld resource ids respectively.

For WebForms there's a special DbResourceControl that automatically generates the resource link for most Web Controls so you don't have to explicitly set the attributes.

Works with pure HTML and SPA Content

The resource editing tags are pure client side HTML attributes driven through JavaScript code. The resource editing features are not tied to any ASP.NET server side code.

For example the Resource Editor Form in the screen shot is a SPA application that runs of an Index.html page and uses AngularJs for all of its UI. All resources are embedded into the page using the JavaScript Resource Handler which serves JavaScript resources from the ASP.NET server to the client.

You can still use the data-resource-id and data-resource-set tags on the client side content:

<div>
    <p data-resource-id="CreateClassInfo">
        {{::view.resources.CreateClassInfo}}
    </p>
    <p data-resource-id="CreateClassInfo2">
        {{::view.resources.CreateClassInfo2}}
    </p> 
    <button id="btnGo" ng-click="view.GoHome()" 
            data-resource-id="btnGo">Go Home</button>                           
</div>

In this example, the database resources are fed to JavaScript using strongly typed resources from the JavaScriptResourceHandler and it works just as well for these resources as long as the id's correlate to the database resource ids.

How it works

This resource editing mechanism is based on a client side JavaScript implementation that checks for specific attributes on DOM elements and then injects icon overlays into the HTML document to provide clickable links that open the Resource Editor with the selected resources active.

Attribute based Resource Identification

The icons are attached to HTML elements that have a specific data-resource-id attribute. In addition a data-resource-set attribute has to exist to identify the resource set, either on the same HTML element or at an element along the parent DOM tree.

On typical pages you'll have a set up like this:

<body data-resource-set="LocalizationForm">
   <div class="page-title" 
        data-resource-id="PageTitle">
     @DbRes.T("PageTitle","LocalizationForm")
   </div>

   <-- if you have non-wrapped text you can add a <span> tag -->
   <span data-resource-id="HelloWorld">@LocalizationForm.HelloWorld</span>
</body> 
Any HTML Page can be Marked Up

The data-resource-id attribute can be attached to any element on an HTML page. In the example above the first element is already wrapped into a <div> tag and so I can simply attach the attribute to the parent. In the second example the text originally was just plain free standing text without any wrapping element - I added a <span> tag around the element to allow attaching of the data-resource-id attribute.

Since the logic is based on pure HTML and JavaScript, this mechanism works with any server side as well as any client side technology. Above I'm using DbRes.T() and strongly typed resources as an example here to embed the actual values.

WebForms

In ASP.NET WebForms pages that use meta:resourcekey tags you can use a DbResourceControl that can automatically generate the data-resource-id and data-resource-set attributes for any WebControls on the page so you don't have to manually create the attributes.

Pure Client Side and AngularJs

For client side code the usage is still the same. For example the following uses AngularJs binding expressions and the usage of the attributes is identical:

<li>
    <a ng-show="view.resourceSets && view.resourceSets.length > 0"
        data-toggle="modal" data-target="#CreateClassDialog"
        title="{{view.resources.CreateClass_Title}}"
        data-resource-id="CreateClass">
        <i class="fa fa-code" style="font-weight: bold;"></i> {{::view.dbRes('CreateClass')}}
    </a>
</li>
<li>
    <a ng-show="view.resourceSets && view.resourceSets.length > 0"
        ng-click="view.onReloadResourcesClick()"
        title="{{view.dbRes('ReloadResources_Title')}}"
        data-resource-id="ReloadResources">
        <i class="fa fa-refresh"></i> {{::view.dbRes('ReloadResources')}}
    </a>
</li>

Again notice the data-resource-id attributes in both of the elements. Note that in this case both elements have two resources exposed - the value and the title and you can only link to one of them. However the UI shows the CreateClass.Title as a subitem of CreateClass so it's easy to jump to this subitem.

What gets generated

Behind the scenes the client-side library runs through the document and essentially injects DOM elements into the page that represent the icons. The icons are overlaid ontop of the existing DOM element as a small semi-transparent icon that is clickable.

The actual rendered runtime HTML for one of the icons and it's host element looks like this:

<!-- injected element -->
<res-edit class="resource-editor-icon" 
          target="resourceEditor" 
          title="Edit resource"></res-edit>

 <!-- original element -->
 <button class="btn btn-sm btn-default ng-binding" 
         title="Add new ResourceSet" 
         ng-click="view.onAddResourceClick()" 
         data-resource-id="Add">
    <i class="fa fa-plus"></i> Add
</button>

Why a <res-edit> tag? I used a custom tag name to avoid any CSS bleed issues with links or div tags. I had originally used an anchor tag, but I ran into formatting problems as CSS formatting for links would affect the rendering. Using a custom tag minimizes - but doesn't entirely remove - these problems. The CSS uses absolute positioning with a high zIndex value and opacity to overlay existing content so the impact of the icons should be minimal.

Enabling/Disabling Edit Mode

In typical applications you'll want to provide resource edit links only in special admin modes and even then only when you explicitly decide to edit resources. Otherwise the icons just get in the way, as well as causing overhead in their generation into the page.

For this reason the icons are not visible by default and you have to explicitly call the showResourceIcons() function.

For example, in the LocalizationAdmin edit form I enable and disable resource editing with two menu options which are alternately displayed or hidden depending on the state of a view variable in the AngularJs model.

Here's what the click code that is called from either of those buttons looks like to enable and disabling resource editing:

// initial state
vm.showResourceIcons = false;

// toggle handler from menu option or button typically
vm.showResourceIcons = function () {
    vm.resourceEditMode = !vm.resourceEditMode;  // toggle mode
    if (vm.resourceEditMode)
        ww.resourceEditor.showResourceIcons({ adminUrl: "./" });
    else
        ww.resourceEditor.removeResourceIcons();
};

You can check out this behavior in the actual Localization Admin form which is localizable and then links to itself in edit mode.

Allowing Resource Editing Access

You will also want to determine whether you want to allow resource editing at all from the server side by not even embedding the library link or startup code based on whether the user has rights.

For example in a Razor view you might want to do:

@{
   bool allowResourceEditing = user.IsAdmin && App.Configuration.AllowResourcEditing
}    

And then in your template where you load the script and startup code:

Using the Resource Edit Button:

@if (allowResourceEditing)
{
<script src="~/localizationAdmin/scripts/ww.resourceEditor.js"></script>
<script>
    ww.resourceEditor.showEditButton({ adminUrl: "./localizationAdmin/" });
</script>
}

or you can manually hook up the edit functionality in your own UI:

@if (allowResourceEditing)
{
<script src="~/localizationAdmin/scripts/ww.resourceEditor.js"></script>
<script>
    var toggleEditMode = false;
    $("#btnEditResources").click(function() {
        toggleEditMode = !toggleEditMode; 
        if(toggleEditMode)
            ww.resourceEditor.showResourceIcons({ adminUrl: "./localizationAdmin/" });
        else
            ww.resourceEditor.removeResourceIcons();
    });
</script>
}

This avoids loading the library when the user isn't authorized to access the resource editing features in the first place.

Resource Editing for CSM-like Features

The ability to embed resource editing links into a page allow for basic CMS like features. It's very easy to use this functionality for creating user editable content in a Web site both for localization and even for non-localization content.

Especially combined with the Markdown Feature for content, you can easily create editable content areas that are easy to edit by administrative users at runtime.

If your content editing needs are simple this globalization library makes it easy to add editable content to any ASP.NET Web application.

Summary

Resource editing with linkable resources is very useful in dynamic applications that allow editing of content for localization (as well as basic content editing). With the MarkDown support for resources this too can be extremely useful for CMS like features in applications where you want to allow editing of content even if it isn't localizable.

Although it requires a little bit of setup, plus adding extra attributes onto content, this feature provides an easy way to make applications dynamically editable for localization. The DbResourceControl for WebForms provides an even easier approach as most WebControls can automatically be linked for resource editing.

Related Resources

⚠️ **GitHub.com Fallback** ⚠️