NAV Navbar
  • BEE plugin walkthrough
  • HTML (index.html)
  • JAVASCRIPT (example.js)
  • EXAMPLE APPLICATION (example.js)

  • BEE plugin walkthrough

    A tutorial with actual code.

    So, you want to install the HTML design editor, BEE as a plugin and you just want to dive into the code? Well, let's do it! It's not that hard.

    Included here is a complete HTML and Javascript sample code with detailed explanations of what's going on.

    Download the sample code

    Live demo

    HTML (index.html)

    <!doctype html>
    <html>
    <head>
        <meta charset="UTF-8">
    

    The BEE plugin has to be loaded from an HTML web page. This example index.html shows the bare bones necessary to load and display the plugin.

    Dependencies

        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
        <script src="https://app-rsrc.getbee.io/plugin/BeePlugin.js" type="text/javascript"></script>
        <script src="example.js" type="text/javascript"></script>
    

    All you need to do is load the BeePlugin.js from our server. The rest of the code to drive the tutorial is in example.js.

    The Container

        <style type="text/css">
          #bee-plugin-container {
            position: absolute;
            width: 100%;
            height: 100%;
          }
        </style>
    </head>
    <body>
        <div id="bee-plugin-container"></div>
    

    BEE needs a block element like a <div/> to load itself into. In this case #bee-plugin-container.

    NOTE: It's important to set an explicit viewport size (height and width) for the container. Otherwise, BEE will load into the height and width of what will be an empty div, which will be too small.

    Authorization

    Replace the client_id and client_secret with your own from https://developers.beefree.io/apps

        <script>
            var client_id = "2c62ed3d-7ced-48c2-acfd-d1ed2e01a7c7";
            var client_secret = "kWqBZj2IMasRXAnpjO5S71UeEG9hwGcxy1RinSA7OA1LylESlJG";
        </script>
    

    BEE plugin authorizes using OAuth. You need a Client ID and Client Secret. These keys are from our account. To use your account, get the Client ID and Client Secret from https://developers.beefree.io/apps

    Running the tutorial

    Once you have the plugin script loaded and a container, you can load the BEE plugin into the web page. In this tutorial, we'll do it 2 different ways.

    Simple load example

        <script>
            loadBeePlugin(
                client_id, 
                client_secret, 
                getBeeConfig(
                    '42',                  // user_id
                    'bee-plugin-container' // container_id
                ), 
                'https://rsrc.getbee.io/api/templates/m-bee'
            );
        </script>
    

    If you just want to see the plugin run when you load the page use this script.

    Application flow example

        <script>
            var app = new BeeApp(
                client_id, 
                client_secret, 
                getBeeConfig(
                    '42',                   // user_id
                    'bee-plugin-container'  // container_id
                )
            );
            app.start();
        </script>
    

    If you want to see more of a complete application, run the full tutorial application.

    End of index.html

    </body>
    </html>
    

    JAVASCRIPT (example.js)

    This example Javascript example.js shows both the basic bare bones necessary to load and display the plugin and an example application that shows a more complete flow.

    TUTORIAL DEPENDENCIES

    $(document).ready(function(){
      (function() {
        function load_css(filename) {
          var link=document.createElement("link");  
          link.setAttribute("rel", "stylesheet");
          link.setAttribute("type", "text/css");
          link.setAttribute("href", filename);
          document.head.appendChild(link);
        }
        function load_script(filename, callback) {
          var script=document.createElement("script");
          script.setAttribute("type","text/javascript");
          script.setAttribute("src", filename);
          script.onload = callback;
          document.body.insertBefore(script, document.body.firstChild);
        }    
        if(!$.fn.popover) {
          load_script("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js");
          load_css("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css");
        }
        if(!window._) {
          load_script(
            "https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js",
            function() {
              // Use mustache-style interpolation
              _.templateSettings = {
                escape:   /\{\{([\s\w].+?)\}\}/g,
                evaluate: /\{\{\{(.+?)\}\}\}/g,
                interpolate: /\{\{=(.+?)\}\}/g,
              };
            }
          );
        }
        if(!window.html2canvas) 
          load_script("https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js");
        if(!window.BeePlugin) 
          load_script("https://app-rsrc.getbee.io/plugin/BeePlugin.js");
      })();
    })
    

    The only thing BEE needs to run is its own Javascript file. Normally you'd just include it in the <head/> like so:

    <script src="https://app-rsrc.getbee.io/plugin/BeePlugin.js" type="text/javascript"></script>

    We'll load it dynamically in case it hasn't been loaded yet, but you probably won't need to do this.

    CONFIGURATION

    BEE is a HTML design editor packaged as a UX component. You load it to edit some HTML document and you get back the new HTML document.

    In order to fit the component into your application's work flows, BEE relies on the configuration to pass in as much application context as possible before it loads.

    While you can do a lot, the only two things you absolutely must pass into BEE are the user_id and container_id. So, for the tutorial, that's all we are asking.

    function getBeeConfig(user_or_account_id, container_id) {
      return {
    

    MANDATORY CONFIGURATION

    The user_id and container_id are the only two configuration variables BEE absolutely needs to load the plugin.

    User ID

    TUTORIAL NOTE: Forcible prefixing '__' ensures the 3 character minimum.

          uid: '__'+user_or_account_id, 
    

    mandatory

    An alphanumeric string matching /[a-zA-Z0-9_\-]{3,}/ (at least 3 characters)

    You must pass in a user ID to BEE; the user ID is whatever your app considers an account. An individual user, team, or a whole organization using your app.

    To decide, the key point is that users in the same BEE account share images in the file picker.

    Usually your app will pass in the team account ID. However, if individual users of your app upload images that must be kept separate from other users, you would pass in their user account.

    HTML container ID

          container: container_id,
    

    mandatory

    A string, which is the HTML id of the element (e.g. a <div/>) that will contain the BEE plugin

    MARKETING AUTOMATION

    BEE is the same editor used in MailUp, a powerful email marketing application. It comes out of the box with features that connect the emails created in BEE back to workflows in your host application.

          specialLinks: [{
              type: 'Subscription',     // Submenu name that groups links
              label: 'Unsubscribe',     // Menu label
              link: '{{unsubscribe}}'   // HREF emitted in the HTML
            }, {
              type: 'Subscription',
              label: 'Subscribe', 
              link: '{{subscribe}}' 
            }, {
              type: 'Content',
              label: 'BEE plugin documentation', 
              link: 'https://docs.beefree.io' 
            }
          ],
    

    optional default: []

    key description
    type Submenu name that groups special links
    label The link text displayed to users
    link The HREF emitted in the HTML (or template fragment for post-processing)

    Special links are usually application links like email life-cycle management, profile pages, login URLs, etc. They can also be links to a content library like sales collateral.

    The actual link URLs may not be known at the time the BEE editor is loaded. In that case, you can use a merge tag format like {first_name}, *|FIRST_NAME|*, [first_name] that will be emitted in the HTML. They can be in whatever format your application will parse to transform into the final HTML; typically the format of the template engine you use.

    You should always provide an Unsubscribe link if you're doing commercial email.

    Merge tags

          mergeTags: [{
              name: 'First Name',     // The menu label
              value: '{{first_name}}' // What's emitted in the HTML for post-processing
            }, {
              name: 'Latest order date',
              value: '{{order_date}}'
          }], 
    
    key description
    name The menu label for the merge tag
    value What's emitted in the HTML for post-processing

    Merge tags are strings like {first_name}, *|FIRST_NAME|*, [first_name] that will be emitted in the HTML. They can be in whatever format your application will parse to transform into the final HTML; typically the format of the template engine you use.

    Merge content

          mergeContents: [{
            name: 'Headline news',  // The menu label for the merge content item
            value: '{{=headline}}'  // What's emitted in the HTML for post-processing
          }, {
            name: 'Image of last product viewed',
            value: '{{=last_product_viewed}}'
          }],
    

    optional default: []

    key description
    name The menu label for the merge content item
    value What's emitted in the HTML for post-processing

    Merge content are exactly like merge tags, except that they are presented to the user in the editor interface as a content block.

    The values are strings like {first_name}, *|FIRST_NAME|*, [first_name] that will be emitted in the HTML. They can be in whatever format your
    application will parse to transform into the final HTML; typically the format of the template engine you use.

    Conditional rules

          rowDisplayConditions: [{
            // Submenu name that groups conditionals
            type: 'Last ordered catalog',
    
            // The menu label for the conditional rule
            label: 'Women',
    
            // Description displayed in the menu to explain the purpose of the conditional
            description: 'Only people whose last ordered item is part of the Women catalog will see this',
    
            // Template fragment emitted in the HTML before the conditional content block
            before: '{{{ if(lastOrder.catalog == "women") { }}}',
    
            // Template fragment emitted in the HTML after the conditional content block
            after: '{{{ } }}}',
          }],
    

    paid only optional default: []

    key description
    type Submenu name that groups conditionals
    label The menu label for the conditional rule
    description Description displayed in the menu to explain the purpose of the conditional
    before Template fragment emitted in the HTML before the conditional content block
    after Template fragment emitted in the HTML after the conditional content block

    Allow users to create email sections that are optionally displayed or hidden based on conditions to create configurable, segmentable emails.

    Conditions are applied to content blocks or content rows. All that happens is that the before: and after: strings are emitted in the resulting HTML before or after the content block or row, much like CSS :after and :before. BEE does not care about what the contents of the strings are.

    This means the syntax can fit whatever syntax your application's templating engine requires.

    EDITOR CUSTOMIZATION

    BEE's behavior is configurable and customizable to meet the specifics of your application.

    Roles and permissions

          // roleHash: undefined,
    

    paid only optional default: undefined

    A string equal to the role hash defined from https://developers.beefree.io/apps

    It's common to design templates where some sections are only editable by certain users, such as legal disclaimers, unsubscribe footers, or brand elements. On paid accounts, you can create templates that have locked content sections that should not be editable by certain types of users. When roles are used, users will be able to choose which sections to lock when in the BEE design editor.

    The individual user roles are defined from https://developers.beefree.io/apps

    Autosave

          autosave: 30, 
    

    optional default: false

    How often to auto-save in seconds. Set to false to disable. The minimum value is 15. See the onAutoSave() event handler.

    Prevent close

          preventClose: false,
    

    optional default: false

    If true, show an alert to ask the user to save before the browser closes.

    Language localization

          language: 'en-US',
    

    optional default: en-US

    An ISO 639-1 regional format (xx-YY).

    One major advantage of BEE is that it is already localized in almost every major language.

    If the language you want is not available, contact support@beefree.io. Localizing the editor is not hard, and we could use your help writing the translation table!

    Custom fonts

          editorFonts: {
              // If false, show only the list in customFonts...
              showDefaultFonts: true,
    
              // An array of your custom fonts...
              customFonts: [{
                  // Menu label for the font
                  name: "Lobster",
    
                  // String emitted for the CSS "font-family" attribute
                  fontFamily: "'Lobster', Georgia, Times, serif",
    
                  // https link to Web Font CSS file with @font-face declarations  
                  url: "https://fonts.googleapis.com/css?family=Lobster"
              }]
          },
    

    optional default: {'showDefaultFonts': true}

    key description
    showDefaultFonts If false, show only the list in customFonts...
    customFonts An array of your custom fonts...
    name Menu label for the font
    fontFamily String emitted for the CSS font-family attribute
    url https link to Web Font CSS file with @font-face declarations

    Provide a collection of custom fonts to add to the the editor.

    You are not limited to the font selection that BEE has out of the box, although every default font BEE provides will have some acceptable rendering in all major email clients.

    You can provide fonts by CSS font-family, or you can use the powerful Web Font standard to add incredible online fonts.

    Note that many email clients do not support newer font technologies That's why BEE uses "font stacks" to allow for graceful degradation.

    Content dialog

        contentDialog: {
          specialLinks: {
            label: 'Custom special link',
            handler: BeeApp.callback('contentDialog.specialLinks')
          },
          mergeTags: {
            label: 'Custom merge tag',
            handler: BeeApp.callback('contentDialog.mergeTags')
          },
          mergeContents: {
            label: 'Custom dynamic content',
            handler: BeeApp.callback('contentDialog.mergeContents')
          },
          rowDisplayConditions: {
            label: 'Custom row condition',
            handler: BeeApp.callback('contentDialog.rowDisplayConditions')
          },
        },
    

    paid only optional default: undefined

    You can extend the BEE editor with your own user interface to pick design elements like special links, merge tags, and row conditions. This is an example UX to demonstrate content dialogs.

    EVENT HANDLERS

    BEE is the HTML design editor only. In order to connect to your application to implement a full email application, it provides a rich set of hooks through its API.

    To capture the output from the editor, you must implement event handlers. And while they are all optional, we strongly recommend you implement at least onSave().

    What are the 2 output formats for?

    The editor generates 2 types of documents:

    type description
    htmlFile The HTML generated for the document
    jsonFile A JSON structural version of the document

    The HTML generated is the purpose of the BEE editor. However, if you're using merge tags, merge content, conditionals, special links, or dynamic images, you will need to do a post-processing step to generate the final HTML. Typically running it through whatever templating engine your application uses.

    The JSON file is the internal version of the document to the BEE editor. It's what you should save and pass back to the BEE editor (as the template) to reload the same document, or reuse it as a template.

    The reason why it is JSON is that BEE is continually updating its HTML generator to keep up with the latest changes in email clients, browsers, and devices. The JSON is what is sent to the BEE servers to be parsed and transformed into the final HTML.

    onLoad(jsonFile)

        onLoad: function(jsonFile) {},
    

    Callback, once the template is loaded and the editor is ready to use.

    onAutoSave(jsonFile)

        onAutoSave: function(jsonFile) {},
    

    Callback upon auto-save. Either save the JSON document into your application database, or save it in localStorage.

    To enable, must set the autosave configuration variable.

    onSave(jsonFile, htmlFile)

        onSave: function(jsonFile, htmlFile) {},
    

    Callback when the user saves the document.

    Save the JSON to reload into the editor when the user wants to either

    1. edit the document again
    2. use the document as a template

    Save the HTML to use; e.g. send as an email or use as a landing page. The HTML will require post-processing if you're using any marketing automation features.

    onSaveAsTemplate(jsonFile)

        onSaveAsTemplate: function(jsonFile) {},
    

    Callback when the user asks to Save as Template

    Save the JSON document as a template.

    NOTE: It's usually better to just let the user save the document directly (using onSave()) in order to get the HTML document as well to generate a thumbnail. Then from a directory of saved documents, allow users to clone a message as a template.

    onSend(htmlFile)

        onSend: function(htmlFile) {}, 
    

    Callback when the user wants to send the document as an email.

    The HTML will require post-processing if you're using any marketing automation features. Also, you'll of course need to get the subject line, to:, cc:, bcc: as well. Then use whatever service like Sendgrid, Mandrill, Amazon SES to send the email.

    onError(errorMessage)

        onError: function(errorMessage) {}
      };
    }
    

    Callback upon error.

    SIMPLE INITIALIZATION

    The following shows the simple load technique to get BEE plugin running. Normally for production purposes, you'd would use the more developed technique in the example application, but this technique will work fine while you are testing BEE plugin internally.

    Authorization

    function authorizeBee(client_id, client_secret, callback) {
      $.ajax({
          method: 'POST',
          url: 'https://auth.getbee.io/apiauth',
          data: { grant_type: 'password', client_id: client_id, client_secret: client_secret }
      }).done(callback);
    }
    

    BEE uses OAuth to authorize use of the plugin. You need the Client ID and Client Secret you get from https://developers.beefree.io/apps

    POST grant_type=password&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET to https://auth.getbee.io/apiauth

    If successful, you'll get a refresh token suitable to pass into the plugin. The plugin will automatically refresh the token.

    It's strongly recommended you authorize the plugin from the server to protect the Client Secret from being stolen. Then pass the resulting refresh token into the Javascript context in the browser. However, you can do it all from Javascript as you see below.

    Load plugin

    function loadBeePlugin(
      client_id, 
      client_secret, 
      bee_config, 
      template_url
    ) {
      authorizeBee( client_id, client_secret, function(token) {
        $.ajax(template_url, {dataType:'json'}).done( function(template) {
          BeePlugin.create(token, bee_config, function(bee) {
            bee.start(template);
          });
        });
      });
    }
    

    To load the BEE plugin, you only need the following bits of information:

    1. Authorization context.

      Client ID and Client Secret from https://developers.beefree.io/apps or (better) the Refresh Token.

    2. BEE configuration.

      Specifically, you only NEED these 2 configuration parameters { uid: 'unique_identifier_of_the_user', container: 'div_id_to_inject_the_plugin_into' }

    3. JSON template.

      A JSON document in the BEE format (generated by onSave()) to initialize the editor with.

    EXAMPLE APPLICATION (example.js)

    function BeeApp(client_id, client_secret, bee_config) {
      BeeApp.singleton = this;
      BeeApp.singleton.callbacks = {};
      var that = this;
      this.bee_config = bee_config;
    

    This is a very simple application that demonstrates some core workflows and concepts of building a BEE application. You'll find it in example.js in the sample code.

    LIVE DEMO

    OFFSCREEN STAGING

      /* Use the leftside as a staging area */
      function offscreenElement(element) {
        if(element) {
          element.oldStyle = {'position': element.style.position, 'left': element.style.left };
          element.style.position = 'absolute';
          element.style.left = '-999999px';
        }
      }
      function onscreenElement(element) {
        if(element && element.style) {
          element.style.position = element.oldStyle.position;
          element.style.left = element.oldStyle.left;
        }
      }
    

    Use the leftside as a staging area to make transitions smoother.

    HTML PROCESSOR

      function HTMLProcessor() {
    

    An HTMLProcessor instance is used to post-process HTML being returned from the BEE editor onSave(), etc.

    Thumbnails

        function html2png(html, callback) {
          var doc = document;
          var iframe = document.createElement('iframe');
          iframe.style.width = '1024px';
          offscreenElement(iframe);
          doc.body.appendChild(iframe);
    
          iframe.contentWindow.document.body.innerHTML = html;
          html2canvas(iframe.contentWindow.document.body, {
            onrendered: function(canvas) {
              callback(canvas.toDataURL("image/png"));
              doc.body.removeChild(iframe);
            }
          });
        }
    

    Quite often you'll want to generate thumbnails of the generated HTML. This is one method to do so in the client browser.

    It's recommended you take a screenshot on the server, using PhantomJS. e.g. phantom rasterize.js http://www.google.com google.png

    HTML Transformer

        function transformHtml(html) {
          var variables = {
            'unsubscribe': 'http:unsubscribe.php?uid=' + bee_config.uid + '&email=' + 'EMAIL',
            'subscribe': 'http:subscribe.php?uid=' + bee_config.uid,
            'first_name': 'Jane',
            'order_date': 'June 1, 2017',
            'headline': '<h1>I am a headline!</h1>',
            'last_product_viewed': '<p><img src="https://beefree.io/wp-content/themes/bee/images/bee-logo.png"/></p>',
            'lastOrder' : {'catalog': 'men'},
            'product': { 
              0: '<h2>Running shoes</h2>',
              1: '<h2>Dress shoes</h2>',
              2: '<h2>Tap shoes</h2>',
              'description': 'Running shoes',
              'price': '$99.99',
              'category': 'shoes',
            },
            'customer': {'revenue': 42, 'hasAbandonedCart': true }
          };
    
          // Use mustache-style interpolation
          //
          // {{normal_variable}}               string replacement but escape HTML characters
          // {{=html_variable}}                string replacement, but let raw HTML go through
          // {{{ if(conditional==value) { }}}  logical expressions 
          //    ...block...
          // {{{ } }}}
          //
          // TUTORIAL NOTE:
          // You really only have to set this once, but it's repeated here so it's
          // obvious what's happening to the reader.
          _.templateSettings = {
            escape:   /\{\{([\s\w].+?)\}\}/g,
            evaluate: /\{\{\{(.+?)\}\}\}/g,
            interpolate: /\{\{=(.+?)\}\}/g,
          };
    
          return _.template(html)(variables);
        }
    

    The HTML you get back from BEE is not ready to send. It still needs to be transformed if you're using any of the marketing automation features, such as merge tags. We recommend you use a templating engine like Handlebars, Liquid to do the transformation step.

    Take note of the unsubscribe link. Normally you'd generate a random GUID that would map the uid and the recipient email address.

    Calling the HTML Processor

        this.process = function(html, callback) {
          html = transformHtml(html);
          html2png(html, function(png){
            callback(html, png)
          });
        }
      }
    

    The main method called from onSave(), etc.

    EVENT CALLBACKS

      function BeeCallbacks(app, bee_config) {
        this.app = app;
    

    BEE communicates back with your application through callbacks on events fired mostly when the toolbar menu is used. This shows you how to hook them effectively.

    onLoad

        bee_config.onLoad = function() { app.onLoadEditor() }
    

    Implementation of onLoad().

    When the BEE plugin has initialized, the tutorial does a smooth transition to show it. BEE comes with a built-in loading screen if you don't want to build one.

    onAutoSave

        bee_config.onAutoSave = function(jsonFile) {
          window.localStorage.setItem('autosave.json', jsonFile);
        },
    

    Implementation of onAutoSave().

    Save the document for reload in case the browser crashes or the user session expires in the middle of their work.

    onSave

        bee_config.onSave = function(jsonFile, htmlFile) {
          window.localStorage.setItem('save.json', jsonFile);
          app.html_processor.process(htmlFile, function(html, png){
            window.localStorage.setItem('save.html', html);
            window.localStorage.setItem('save.png', png);
          });
        },
    

    Implementation of onSave().

    Save both the JSON and the post-processed HTML. It's a good idea to grab a thumbnail when saving.

    onSend

        bee_config.onSend = function(htmlFile) {
          app.html_processor.process(htmlFile, function(html, png){
            window.localStorage.setItem('save.html', html);
            window.localStorage.setItem('save.png', png);
            app.onSend(html, png);      
          });
        }, 
    

    Implementation of onSend().

    Move onto sending the email. What happens here depends on the use case, like getting the recipient email, or the mailing list. Getting the subject. Scheduling a time to send. Etc.

    Your application has to actually send the email using something like Sendgrid, Mandrill or Amazon SES.

    onError

        bee_config.onError = function(errorMessage) { app.onError(errorMessage) }
      }
    

    Implementation of onError().

    Capture and display any errors.

    SPINNER

      function Spinner() {
        this.start = function() {
          var div = document.createElement('div');
          div.style.position='absolute';
          div.style.top = '0';
          div.style.left = '0';
          div.style.width = '100%';
          div.style.height = '100%';
          div.innerHTML =  "<table style='width:100%;height:100%;''><tr><td style='width:100%;height:100%;text-align:center;vertical-align:middle'><img src='http://cdnjs.cloudflare.com/ajax/libs/galleriffic/2.0.1/css/loader.gif'/></td></tr></table>";
          document.body.appendChild(div);
          this.container = div;
          this.hide();
        }
        this.show = function() { onscreenElement(this.container); }
        this.hide = function() { offscreenElement(this.container); }
      }
    

    You can use your own wait spinner if you want by carefully hiding and showing the plugin by hooking onLoad(). However, BEE comes built in with its own loading screen so this is unnecessary.

      function BeeTemplateGallery() {
        var that = this;
    

    You'll need to create a template gallery, both from prebuilt templates and by cloning saved emails (thereby letting users create new templates). Templates are really just saved emails that are cloned to make new emails.

    If you have an autosave implemented, you may also want to let the user recover their last document.

    A template gallery is really very simple. Templates have a name, a thumbnail, and an associated JSON file that you send into BEE when loading the plugin.

    Render template gallary

        this.render = function() {
          function renderCell(cell) {
            var result = [
              "<div class='col-md-4' style='padding:16px'>",
              "  <div style='border:solid 1px #eee;border-radius:4px;padding:16px'>",
              "    <h3>" + cell.name + "</h3>",
              "    <p><img src='" + cell.thumbnail + "' width='100' height='100'/></p>",
              "    <p><a class='btn btn-primary' role='button' href='javascript:void(0)' onclick='BeeApp.singleton.useTemplate(\""+cell.id+"\")'>Use this template</a></p>",
              "  </div>",
              "</div>",
            ];
            return result.join("\n");
          }
    
          function renderRow(row) {
            var result = ["<div class='row'>"];
            for(var i = 0; i < row.length; i++) {
              result.push(renderCell(row[i]));
            }
            result.push("</div>");
            return result.join("\n");
          }
    
          // copy array so we can non-destructively splice it
          var templates = this.templates.slice(); 
          var result = [];
          while(templates.length > 0) {
            var row = templates.splice(0, 3);
            result.push(renderRow(row));
          }
          this.container.innerHTML = result.join("\n");
        }
    

    Display rows of template thumbnails with buttons to Use this template, which selects which JSONfile we will pass into BeePlugin.create() later.

    Template database

        var no_thumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII='
        this.templates = [
          {"name": "Basic E-commerce", "json": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-basic-e-commerce.json", "thumbnail": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-basic-e-commerce.png"},
          {"name": "Basic Newsletter", "json": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-basic-newsletter.json", "thumbnail": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-basic-newsletter.png"},
          {"name": "Basic One-Column", "json": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-basic-onecolumn.json", "thumbnail": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-basic-onecolumn.png"},
          {"name": "Basic Standard", "json": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-basic-standard.json", "thumbnail": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-basic-standard.png"},
          {"name": "Blank Template", "json": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-blank-template.json", "thumbnail": no_thumbnail},
          {"name": "Ecommerce Template", "json": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-ecommerce-template.json", "thumbnail": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-ecommerce-template.png"},
          {"name": "Newsletter", "json": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-newsletter-template.json", "thumbnail": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-newsletter-template.png"},
          {"name": "Promo Template", "json": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-promo-template.json", "thumbnail": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-promo-template.png"},
          {"name": "Simple Template", "json": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-simple-template.json", "thumbnail": "https://raw.githubusercontent.com/BEE-Plugin/BEE-FREE-templates/master/v.2/BF-simple-template.png"},
        ];
    

    The templates are available in Github for your own use. However, you must serve them from your own application server.

    Using saved emails as tempaltes

        if(localStorage['save.json']) {
          this.templates.unshift(
            {"name": "Saved", "json": localStorage['save.json'], "thumbnail": localStorage['save.png']},
          );
        }
    

    Saved emails can also be cloned and used as templates. In fact, that's what templates really are.

    Autosave recovery

        if(localStorage['autosave.json']) {
          this.templates.unshift(
            {"name": "Autosaved", "json": localStorage['autosave.json'], "thumbnail": no_thumbnail},
          );
        }
    

    It's a good idea to allow autosave recovery. Just use the autosave file as a template.

        // Index the templates
        for(var i=0;i < this.templates.length; i++) {
          this.templates[i].id = i;
        }
    
        this.start = function() { 
          if(!this.container) {
            this.container = document.createElement('div');
            document.body.appendChild(this.container);
            this.hide();
            this.render();
          }
        };
        this.show = function() { onscreenElement(this.container); };
        this.hide = function() { offscreenElement(this.container); };
        return this;
      }
    

    The remaining code acts as the controller for the template gallery.

    TOOLBAR

      function BeeToolbar() {
        this.start = function(pluginContainer) {
          var div = document.createElement('div');
          var result = ["<nav class='navbar navbar-default'><div class='container'>"];
          result.push("<a class='btn btn-primary navbar-btn' role='button' href='javascript:void(0)' onclick='BeeApp.singleton.toggleGallery()'>Template Gallery</a>");
    
          var buttons = [
            ['Preview', 'preview'],
            ['Save', 'save'],
            ['Save As Template', 'saveAsTemplate'],
            ['Send Email', 'send'],
            ['Toggle Structure', 'toggleStructure'],
          ];
    
          for(var i=0; i < buttons.length; i++) {
            var label = buttons[i][0];
            var method = buttons[i][1];
            result = result.concat([
              "<a class='btn btn-primary navbar-btn' role='button' href='javascript:void(0)' onclick='BeeApp.singleton.editor.plugin."+method+"()'>"+label+"</a>"
            ]);
          }
          result.push("</div></div>");
          div.innerHTML = result.join("\n");
          pluginContainer.parentNode.insertBefore(div, pluginContainer);
          pluginContainer.style.height = "calc(100% - "+$(pluginContainer).offset().top+"px)";
          this.container = div;
          this.hide();
        }
        this.show = function() { this.container.style.display='block'; }
        this.hide = function() { this.container.style.display='none';  }
      }
    

    You can turn off the BEE built in toolbar and implement your own in your application user interface.

    CONTENT DIALOG

      function BeeContentDialog() {
        var that = this;
        var beeContentDialog = this;
        this.container = document.createElement('div');
        this.container.id="content-dialog";
        this.container.classList.add('modal', 'fade');
        this.container.setAttribute('aria-hidden', 'true');
    

    You can extend the BEE editor with your own user interface to pick design elements like special links, merge tags, and row conditions. This is an example UX to demonstrate content dialogs.

    CALLBACKS

    Helper function

        function showContentDialogCallback(catalogList) {
          return function(resolve, reject) {
            var data = beeContentDialog.show(catalogList);
            data.done(function(selection) {
              resolve(selection);
            });
            data.fail(function() {
              reject();
            });
          }
        }
    

    This helper function builds a callback function to launch a content dialog to pick any design element like special links, merge tags, etc. as defined by the catalogList.

    To launch a content dialog, you must pass a callback function in the BEE editor configuration of the form

    function(resolve, reject)

        BeeApp.singleton.callbacks['contentDialog.specialLinks'] = showContentDialogCallback([{
          // Submenu name that groups special links. If your custom dialog 
          // doesn't care, you can use `'custom`' as a placeholder.
          type: 'custom',
    
          // The link text displayed to users
          label: 'Login link',
    
          // The HREF emitted in the HTML (or template fragment for post-processing)
          link: 'http://www.example.com/account/login'
        }, {
          type: 'custom',
          label: 'Forgot password',
          link: 'http://www.example.com/account/reset'
        }, {
          type: 'custom',
          label: 'Newsfeed',
          link: 'http://www.example.com/user/{{user_id}}/newsfeed'
        }]);
    
    key description
    type Submenu name that groups special links. If your custom dialog doesn't care, you can use 'custom' as a placeholder.
    label The link text displayed to users
    link The HREF emitted in the HTML (or template fragment for post-processing)

    See special links in the configuration for more details.

    Merge tags

        BeeApp.singleton.callbacks['contentDialog.mergeTags'] = showContentDialogCallback([{
          name: 'Product description',      // The menu label for the merge tag
          value: '{{product.description}}'  // What's emitted in the HTML for post-processing
        }, {
          name: 'Product price',
          value: '{{product.price}}'
        }, {
          name: 'Product category',
          value: '{{product.category}}'
        }]);
    
    key description
    name The menu label for the merge tag
    value What's emitted in the HTML for post-processing

    See merge tags in the configuration for more details.

    Merge content

        BeeApp.singleton.callbacks['contentDialog.mergeContents'] = showContentDialogCallback([{
          name: 'Running shoes',  // The menu label for the merge content item 
          value: '{{product[0]}}' // What's emitted in the HTML for post-processing
        }, {
          name: 'Dress shoes',
          value: '{{product[1]}}'
        }, {
          name: 'Tap shoes',
          value: '{{product[2]}}'
        }]);
    
    key description
    name The menu label for the merge content item
    value What's emitted in the HTML for post-processing

    See merge content in the configuration for more details.

    Conditional rules

        BeeApp.singleton.callbacks['contentDialog.rowDisplayConditions'] = showContentDialogCallback([{
          // Submenu name that groups conditionals
          type: 'Customer',
    
          // The menu label for the conditional rule
          label: 'Return customer',
    
          // Description displayed in the menu to explain the purpose of the conditional
          description: 'Has the customer purchased before?',
    
          // Template fragment emitted in the HTML **before** the conditional content block
          before: "{{{ if(customer.revenue > 0) { }}}",
    
          // Template fragment emitted in the HTML **after** the conditional content block
          after: '{{{ } }}}'
        }, {
          type: 'Customer',
          label: 'Abandoned customer',
          description: 'Has the customer abandoned a cart before?',
          before: "{{{ if(customer.hasAbandonedCart) { }}}",
          after: '{{{ } }}}'
        }]);
    
    key description
    type Submenu name that groups conditionals
    label The menu label for the conditional rule
    description Description displayed in the menu to explain the purpose of the conditional
    before Template fragment emitted in the HTML before the conditional content block
    after Template fragment emitted in the HTML after the conditional content block

    See conditional rules in the configuration for more details.

        // Create the modal dialog template and cache it.
        this.start = function() {
          this.container.innerHTML = '\
            <!-- Content Dialog HTML --> \
            <div class="modal-dialog"> \
              <div class="modal-content"> \
                <div class="modal-header"> \
                  <h4 class="modal-title">Choose your content</h4> \
                </div> \
                <div class="modal-body"> \
                  <div class="panel panel-default"> \
                    <div class="panel-body"> \
                    </div> \
                  </div> \
                </div> \
                <div class="modal-footer"> \
                   <button type="button" class="btn btn-default btn-ok" data-dismiss="modal">Confirm</button> \
                   <a class="btn btn-danger" data-dismiss="modal">Cancel</a> \
                </div> \
              </div> \
            </div> \
          ';
          document.body.appendChild(this.container);
          this.body = this.container.getElementsByClassName('panel-body')[0];
        }
    
        // Show the content dialog modal. Build an inventory of
        // content to display from the catalogList.
        this.show = function(catalogList) {
          var modal = new $.Deferred();
          var container = $(this.container);
    
          this.body.innerHTML = '';
          for(var i=0; i < catalogList.length; i++) {
            var item = catalogList[i];
            var button = document.createElement('button');
            button.classList.add('btn', 'btn-primary');
            button.setAttribute('data-dismiss','modal');
            button.innerText = item.name || item.label;
            button.data = item;
            button.style.margin = "8px";
            button.style.display = "block";
            $(button).click(function(){
              modal.resolve(this.data);
            })
            this.body.appendChild(button);
          }
          container.find('.btn-danger').click(function() {
            modal.reject(); // Cancel
          });
          container.on('hidden.bs.modal', function () {
            modal.reject(); // Closed
          });
          container.modal('show');
          return modal.promise();
        }
        this.hide = function() {
          var container = $(this.container);
          container.modal('hide');
        }
        return this;
      }
    

    The remainder of the code builds a small UX: a simple modal dialog to pick from the available content options. Your application can be more complicated of course.

    LOAD PLUGIN

      function BeeEditor() {
        var that = this;
        this.plugin = undefined;
    
        this.start = function(client_id, client_secret, bee_config) {
          this.container = document.getElementById(bee_config.container);
          this.container.style.position = 'absolute';
          this.container.style.width = '100%';
          this.container.style.height = '100%';
          offscreenElement(this.container);
    
          authorizeBee( client_id, client_secret, function(token) {
            BeePlugin.create(token, bee_config, function(bee) {
              bee.start();
              that.plugin = bee;
            });
          });
        }
    
        this.useTemplate = function(template) {
          this.template = template;
        }
    
        this.show = function() {
          if( !this.plugin ) {
            setTimeout( function(){that.show()}, 10 );
            return;
          }
          this.plugin.start(this.template);
          onscreenElement(this.container);
        }
    
        this.hide = function() {
          offscreenElement(this.container);
        }
        return this;
      }
    

    Loading the plugin itself is the same as the simple load method documented above. However, if you want to make the application feel a little bit snappier, you can implement a pre-loading system like the one below. Initialize the plugin in a div offscreen when the page loads. You can send the template in a second step once you know what it will be.

    SEND THE EMAIL

      function BeeSender() {
        this.thumbnail = undefined;
        this.start = function() {
          var iframe = document.createElement('iframe');
          iframe.style.position='absolute';
          iframe.style.top = '0';
          iframe.style.left = '0';
          iframe.style.width = '100%';
          iframe.style.height = '100%';
          document.body.appendChild(iframe);
          this.container = iframe;
          this.hide();
        }
        this.set_html = function(html) { this.html = html }
        this.set_thumbnail = function(src) { this.thumbnail = src }
        this.show = function() { 
          this.container.contentWindow.document.body.innerHTML = this.html;
          onscreenElement(this.container); 
        }
        this.hide = function() { offscreenElement(this.container); }
      }
    

    Sending the actual email is out of scope for this tutorial, but the BEE plugin has a toolbar function for the user to let you know they are ready to send. You may want to process the HTML further if you get more information like the subject line (to put in a meta description and title) or the recipient list to generate unsubscribe links.

    CONTROLLER

    The rest of the code is the application controller for the tutorial app.

    Components

      this.html_processor = new HTMLProcessor();
      this.callbacks = new BeeCallbacks(this, bee_config);
      this.spinner = new Spinner();
      this.templateGallery = new BeeTemplateGallery();
      this.toolbar = new BeeToolbar(this.pluginContainer);
      this.editor = new BeeEditor();
      this.sender = new BeeSender();
      this.contentDialog = new BeeContentDialog();
    

    Load and initialize components.

    Start the application

      this.start = function() {
        this.pluginContainer = document.getElementById(bee_config.container);    
        this.spinner.start();
        this.templateGallery.start();
        this.editor.start(client_id, client_secret, bee_config);
        this.toolbar.start(this.pluginContainer);
        this.contentDialog.start();
        this.sender.start();
        this.templateGallery.show();
      }
    

    Start the application by preloading the plugin, spinner, template gallery toolbar, content dialog, email send stub.

    Start by showing the template gallery.

      this.toggleGallery = function() {
        this.editor.hide();
        this.templateGallery.show();
      }
    

    Hook to show the template gallery again.

    Use template

      this.useTemplate = function(template_id) {
        this.templateGallery.hide();
        this.spinner.show();
        template_url_or_json = this.templateGallery.templates[template_id].json;
        try { 
          template = JSON.parse(template_url_or_json);
          that.editor.useTemplate(template);
          that.editor.show();
        } catch(e) {
          $.ajax(template_url_or_json, {dataType:'json'}).done(function(template) {
            that.editor.useTemplate(template);
            that.editor.show();
          });
        }
      }
    

    Once a template is picked from the gallery, configure the editor with the JSONfile associated with that template.

    Smoothly display the editor

      this.onLoadEditor = function() {
        this.toolbar.show();
        this.spinner.hide();
      }
    

    Called from onLoad to make a smooth transition once and only once the plugin is loaded and ready.

    Sending the email

      this.onSend = function(html, thumbnail) {
        this.editor.hide();
        this.sender.set_html(html);
        this.sender.set_thumbnail(thumbnail);
        this.sender.show();
      }
    

    Called once the user wants to send the email.

    In your application, the HTML will require post-processing if you're using any marketing automation features. Also, you'll of course need to get the subject line, to:, cc:, bcc: as well. Then use whatever service like Sendgrid, Mandrill, Amazon SES to send the email.

    Error processing

      this.onError = function(errorMessage) { 
        console.log(errorMessage); 
        alert(errorMessage); 
      }
    
      return this;
    }
    

    If there are any errors, you should log them and also notify the user in some way.

    Callback map

    BeeApp.callback = function(name) {
      return function() {
        BeeApp.singleton.callbacks[name].apply(null, arguments); 
      }
    }
    

    This creates a facility to create callbacks into the tutorial BEE application from the BEE configuration, which is defined and created before the BEE App is created.