print page  |  view normal version 

Javascript

The Javascript API powers all of Enano's client-side and AJAX user interface bits - and Enano has quite a few of them. All applets share a same core set of functions and variables. These center around loading components from the server, dealing with errors, emulating server-side functionality, and building the dynamic interfaces you've come to expect from Enano. This page attempts to summarize common tasks in Javascript.

Basic variables

title

The URL-name of the current page. Not the page title - the URL!

physical_title

The physical URL-name. If the page you're on is a redirect of some sort, this is the page that the redirect is from, while title will reflect the page that was redirected to.

scriptPath/contentPath/cdnPath

The same as the server side equivalents.

ENANO_SID

If the user has a privileged session, this is the session key.

user_level

The user's highest permitted level of authentication.

auth_level

The current level the user is authenticated to.

USER_LEVEL_{GUEST,MEMBER,CHPREF,MOD,ADMIN}

The same as the server-side constants.

aclDisableTransitionFX

If true, the user has animations turned off. If you use jQuery for your animations, don't worry about manually skipping them because Enano sets jQuery.fx.off to true automatically.

csrf_token

If your plugin does CSRF checks, this is user's current CSRF form token.

Basic functions

Loading components

Enano only loads a small number of Javascript files on each page to minimize requests. Additional components are loaded synchronously on demand using a function called load_component.

load_component takes one argument: either the name of the Javascript file (without the extension .js), or an array of Javascript file names.

// Example: loading one component
load_component('login');
// Loading multiple components
load_component(['jquery', 'jquery-ui']);

Remember that some components depend on others. In most cases, it is your job to satisfy these dependencies! Try to satisfy all dependencies anyway, because you will minimize the number of requests to the server.

If you have Firebug or another Javascript console installed, Enano will log all of the components it loads to the console. If there is an error, an alert will be shown, and the exception will be dumped to the console.

AJAX requests

AJAX requests are done with two functions, ajaxGet and ajaxPost.

ajaxGet()

void ajaxGet(string uri, function readystatechange_callback, bool editor_safe = true)

Makes an asynchronous AJAX request to the server. You'll want to use makeUrl() or makeUrlNS() to generate the URL. (Yes, both are available in Javascript, and they work exactly like their PHP counterparts.)

The readystatechange_callback should be a function that takes one argument, ajax, which is an XMLHttpRequest object. You need to check that ajax.readyState == 4, and probably ajax.status == 200, before accessing the response (ajax.responseText or ajax.responseXML).

The last parameter, editor_safe, should be false if you plan to replace the contents of div#ajaxEditContainer, the div that contains the page contents. This is so that when the editor is open, the user can be warned that they are doing something that will cause them to lose their changes.

Example

load_component('jquery');
setAjaxLoading();
 
ajaxGet(makeUrl('Main_Page', 'noheaders'), function(ajax)
  {
    if ( ajax.readyState == 4 && ajax.status == 200 )
    {
      unsetAjaxLoading();
      $('#ajaxEditContainer').html(ajax.responseText);
    }
  }, false);

ajaxPost()

void ajaxPost(string uri, string post, function readystatechange_callback, bool editor_safe = true)

Identical to ajaxGet(), but takes an additional parameter post which should contain a url-encoded string with the desired POST data. To sanitize a string for inclusion in the POST data, use encodeURIComponent().

Example

var request = {
  mode: 'add_tag',
  tag: 'foo'
};
ajaxPost(
  // URI
  makeUrlNS('Special', 'MyPlugin/AddTag'),
  // POST
  'tag_info=' + encodeURIComponent(toJSONString(request)),
  // handler
  function(ajax)
  {
    if ( ajax.readyState == 4 && ajax.status == 200 )
    {
      if ( !check_json_response(ajax.responseText) )
      {
        handle_invalid_json(ajax.responseText);
        return false;
      }
      var response = parseJSON(ajax.responseText);
      if ( response.mode == 'error' )
        alert(response.error);
      else
        alert('Tag added!');
    }
  });

Visual feedback: setAjaxLoading and unsetAjaxLoading

The functions setAjaxLoading() and unsetAjaxLoading() can be used to control visual feedback that occurs during AJAX requests. Call setAjaxLoading() just before ajaxGet/ajaxPost, and call unsetAjaxLoading() in the ajax.readyState == 4 block.

Example use of these visual feedback functions is included above.

JSON encoding and decoding

The function toJSONString() takes almost any data type and serializes it to a string in JSON format. The function parseJSON() will take a string produced by toJSONString() (or the server-side equivalent enano_json_encode()) and convert it back to whatever type it was originally.

Parse errors

Always use check_json_string() after you receive what you expect to be JSON. It will make sure Enano didn't throw any PHP errors or other garbage that would mess up the JSON parser.

If check_json_string() returns false, call handle_invalid_json(ajax.responseText) and return false from your readystatechange callback, so that the user can be alerted of the error. Reminder: always check for errors!

Server-side JSON processing

The PHP function for encoding JSON is enano_json_encode(), and the function for decoding it is enano_json_decode(). These functions will use PHP's native JSON functions if they are available, and otherwise will fall back to Enano's included JSON API, which is based on the one in the Zend framework plus some Unicode-related fixes.

Dialogs and effects

Enano's range of window and dialog presentation tools varies far and wide. Many AJAX applets in Enano are built on the same underlying API.

Does the user like eye candy?

The Boolean value aclDisableTransitionFX will be true if the user has animations turned off. Please respect this preference when developing your applications. Remember to test your applications with the animation preference both on and off.

jQuery and jQuery UI

Enano uses jQuery as its framework for Javascript automation. You'll need to load the component "jquery" before you can use it however. This can be done with load_component() or the server-side equivalent $template->preload_js().

jQuery UI is used primarily for effects and draggable controls. We include the following components:

  • UI Core
  • Draggable
  • Sortable
  • Effects core
    • Blind
    • Clip
    • Highlight
    • Pulsate
    • Shake
    • Slide

jQuery UI can be loaded as the component name "jquery-ui".

Versions

As with all 3rd-party code, the Enano project makes a strong effort to keep up with jQuery and jQuery UI. As of Enano 1.1.7, the versions in use are jQuery 1.3.2 and jQuery UI 1.7.2, the latest at the time of this writing. Make sure that you only use public APIs when working with jQuery so that you can maintain forward compatibility.

Message Boxes

Pop-down message boxes can be used for small, rich modal dialogs. This API is used in several places but it is most prominently used for the login interface.

class MessageBox(int options, string title, string message_html)

The MessageBox class is used to generate message boxes. Only one message box can be open at a time; please take this into account when you are developing your application.

MessageBox is a class, meaning it must be instanciated (new MessageBox(...) syntax).

Options

The options parameter is a bitfield.

  • MB_OK: Show just the OK button
  • MB_OKCANCEL: Show both OK and Cancel buttons
  • MB_YESNO: Show Yes and No buttons
  • MB_YESNOCANCEL: Show Yes, No, and Cancel buttons
  • MB_ABORTRETRYIGNORE: Show Abort, Retry, and Ignore buttons; why you would ever need this, I don't know.
  • MB_ICONINFORMATION: Show the blue information icon
  • MB_ICONEXCLAMATION: Show the yellow alert icon
  • MB_ICONSTOP: Show the red exclamation-point icon
  • MB_ICONQUESTION: Show the green question-mark icon
  • MB_ICONLOCK: Show the lock icon (for the login window)

You should use one button option and one icon option per call.

var box = new MessageBox(MB_YESNO|MB_ICONEXCLAMATION, 'Do you want to cancel your changes?',
           'Do you really want to discard the changes you have made to this page?');

Onclick events

You can register events to take place when the user clicks any button. The functions will be called after the destruction of the message box window takes place.

box.onclick.Yes = function()
{
  ajaxGet(...);
}
box.onclick.No = function()
{
  alert('You clicked No!');
}

Note that you should use English names for members of box.onclick even if a different language is in use. Enano will take care of all translation automatically.

Onbeforeclick events

The onbeforeclick member can also be used, but it is called before message box destruction takes place. Returning true from callbacks in here will prevent destruction of the message box.

box.onbeforeclick.No = function()
{
  alert('Haha you\'re stuck!');
  return true;
}

miniPrompt

The miniPrompt API was created as a visual alternative to MessageBox. It is more powerful too, as it does not require any specific button scheme or title element; it only creates and floats in the box, letting you decide completely how it behaves. miniPrompt boxes are about 320px wide and can be any height.

HTMLElement miniPrompt(function create_callback)

The miniPrompt() function takes one parameter which is a callback where you are expected to set the HTML inside the prompt. The only parameter passed to this callback is an HTMLElement which is the div you should write to.

The function miniPromptDestroy() takes one parameter, which can be a div owned by the miniPrompt API or any of that div's children. This allows for more elegant code such as <a href="javascript:miniPromptDestroy(this);">.

Example

var prompt = miniPrompt(function(div)
  {
    div.innerHTML = '<h3>Hello world!</h3>';
    div.innerHTML += '<a href="#" onclick="miniPromptDestroy(this); return false;" class="abutton">Close</a>';
  });

miniPrompt's message-box system

There is also a full-fledged system for drawing message boxes with miniPrompt using a JSON-based description code. This system allows for custom text on buttons; developers are encouraged to take advantage of this ability.

The miniPromptMessage() function takes a single parameter, an Object, with the members title, message, and buttons. You can omit either title or message, but not both.

miniPromptMessage({
  title: 'Delete page',
  message: 'Do you really want to delete this page? This is reversible unless you clear the page logs.',
  buttons: [
    {
      text: 'Delete',
      color: 'red',
      style: {
        fontWeight: 'bold'
      },
      icon: scriptPath + '/plugins/myplugin/delete.png',
      // you can also use a sprite here; format is image, width, height, x-offset, y-offset
      // sprite: [ scriptPath + '/plugins/myplugin/icons.png', 16, 16, 0, 48 ],
      onclick: function() {
        ajaxDeletePage();
        miniPromptDestroy(this);
        return false;
      }
    },
    {
      text: 'Cancel',
      onclick: function() {
        miniPromptDestroy(this);
        return false;
      }
    }
  ]
});

Note that in onclick functions there, you should always return false. Supported colors are red, green and blue; omitting the color member will cause the button to be the normal grey abutton style.

Custom CSS can also be specified for buttons using the style member.

Focusing elements in a miniPrompt

The animation effect used in a miniPrompt can cause problems when you want to focus a button or field inside of your dialog box. This is because many browsers, including Firefox and Google Chrome, scroll the window up in order to focus the element. To counter this, perform your focus in a function delayed by 750ms with setTimeout. Remember, however, to not wait if aclDisableTransitionFX is true:

load_component('jquery');
 
var prompt = miniPrompt(function(div)
  {
    div.innerHTML = '<h3>Hello world!</h3>';
    div.innerHTML += '<a id="mpbutton" href="#" onclick="miniPromptDestroy(this); return false;" class="abutton">Close</a>';
  });
 
setTimeout(function()
  {
    $('#mpbutton').focus();
  }, aclDisableTransitionFX ? 0 : 750);

Dynano

Before Enano started shipping jQuery, we used a Javascript framework called Dynano that operates using a similar syntax to jQuery. There are only four functions that are in Dynano that don't have jQuery equivalents; all have to do with our TinyMCE implementation.

Dynano's constructor, $dynano(), takes one parameter: the id of the element you are manipulating. Unlike jQuery, Dynano only operates on one element at a time.

// Convert a textarea to use TinyMCE
$dynano('mytextarea').switchToMCE();
// Convert a TinyMCE editor instance to a normal textarea
$dynano('mytextarea').destroyMCE();
// Get the content of either an MCE instance or a normal textarea (auto-detected)
var text = $dynano('mytextarea').getContent();
// Set the content of an MCE instance or textarea
$dynano('mytextarea').setContent('Hello world!');

Miscellaneous

whiteOutElement()

To show an AJAX spinner over an element, use whiteOutElement(). This returns an HTMLElement which you can later pass to whiteOutReportSuccess() or whiteOutReportFailure(), providing am elegant language-neutral way to show visual feedback on in-progress operations.

It takes 1250ms for the feedback functions whiteOutReportSuccess() and whiteOutReportFailure() to perform. Unless you know that the size of your container will not change, avoid updating the HTML inside the container until after this effect performs to avoid the overlay being the wrong size.

var mydiv = document.getElementById('ajaxEditContainer');
var whitey = whiteOutElement(mydiv);
ajaxGet(makeUrl('Main_Page', 'noheaders'), function(ajax)
  {
    if ( ajax.readyState == 4 && ajax.status == 200 )
    {
      whiteOutReportSuccess(whitey);
      setTimeout(function() {
          $('#ajaxEditContainer').html(ajax.responseText);
        }, 1250);
    }
  }, false);

Logins and Live Re-auth

Enano's login API exposes a few public functions that can be used to authenticate the user without requiring a page reload. This is the Live Re-auth API.

To use Live Re-auth, first check auth_level - if it's as high as your server-side code expects, continue with your function. Otherwise, call Live Re-auth and have it recursively call your function when success is achieved.

void ajaxDynamicReauth(function success_callback, int auth_level)

The success_callback will be called when authentication succeeds. The auth_level should be the same level your code is checking for.

function my_sensitive_operation(page_name)
{
  // touch this variable to put it in the bracket scope so the
  // anonymous function below can access it
  void(page_name);
 
  if ( auth_level < USER_LEVEL_ADMIN )
  {
    ajaxDynamicReauth(function()
      {
        my_sensitive_operation(page_name);
      }, USER_LEVEL_ADMIN);
    // Don't continue the function from here
    return false;
  }
 
  // Continue doing sensitive stuff
  alert('Sensitive stuff');
}

Live Re-auth is about elegance, not security

Remember that Live Re-auth code is javascript - that means that requests from functions that call Live Re-auth should never be trusted! Always check $session->auth_level in your server-side code before allowing any protected functions to operate. Use append_sid() to add the session key to any URLs you request with AJAX, or preferably, use makeURL/makeUrlNS() to generate URLs to a Special page that handles your AJAX requests. The session key is what the server uses to determine the auth_level.

Reminder: Live Re-auth is a way of developing an elegant and responsive user experience. It does not provide any additional security on its own. Always verify permissions on the server.

(show page tags)
Categories: (Uncategorized)
© 2007 Contributors. All content is under the GNU Free Documentation License.
Powered by Enano | Valid XHTML 1.1 | Valid CSS | Time: 0.09s