/**
 * @fileOverview
 * @description <p>Linking this file into a page will automatically enable the backend-to-frontend json-action-API, which relies on JSON requests being dispatched from the frontend to the backend, and the response data structures being executed in accordance with predefined rules as if they were programmatic statements.</p><p>A JSON action request is usually triggered by clicking a link or submitting a form with the special attribute "target" set to the value "json", for example:</p><code>Clicking &lt;a target="json" href="urlToJSONActionView"&gt;here&lt;/a&gt; could cause nifty stuff to happen, such as simultaneously triggering visual effects on several different elements on the page at once.</code><p>The server response to the JSON request should be a dictionary structured in accordance with some predefined syntax rules (see {@link execute_json_actions} for a list of permitted keys and their programmatic significance), and, once received, will trigger a call to {@link execute_json_actions} with the parameter <i>json_data</i> set to the received dictionary and the parameter <i>target_element</i> set to the link or form that triggered the request.</p><p>See {@link execute_json_actions} for a thorough specification of action dictionary syntax.</p>
 * @author <a href="mailto:ludvig@urbantalk.se">Ludvig Svenonius</a>
 */

/**
 * <p>Apply the context-dependent actions in <i>action_dict</i> to <i>element</i>.</p>
 *
 * @param {dom-element} element <p>Element in context of which actions are to be executed.</p>
 * @param {dictionary} action_dict <p>Dictionary containing actions that are to be executed in context of <i>element</i>.</p>
 *   <p>Supported keys:</p>
 *   <table>
 *     <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description</th></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>insertBefore</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>HTML code to be injected right before the context element in the DOM.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>insertAfter</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>HTML code to be injected right after the context element in the DOM.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>replaceHTML</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>HTML code which is to be injected in place of the context element in the DOM.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>properties</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>MooTools properties to set, as if by calling the MooTools <a target="_BLANK" href="http://mootools.net/docs/core/Element/Element#Element:setProperties">Element.setProperties</a> method. Can be used to set element attributes.</p></td></tr> 
 *     <tr><th style="text-align: left; vertical-align: top;"><p>style</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>CSS style attributes to set on the element, inline. Can be used to initialize element styles to wanted values before applying a visual effect.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>effect</p></th><td style="vertical-align: top;"><p>dictionary</p></td>
 *       <td>
 *         <p>A MooTools <a target="_BLANK" href="http://mootools.net/docs/core/Fx/Fx.Morph">Fx.Morph</a> effect to carry out on the context element.</p>
 *         <p>Supported keys:</p>
 *         <table>
 *           <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description</th></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>transitionType</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Name of the MooTools transition type to be used for the effect. Defaults to "Sine" if omitted.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>easeType</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Name of the MooTools ease in/out identifier to be used. Defaults to "easeInOut" if omitted.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>properties</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>Element properties to be modified by the effect. Keys are property names; values are property target values.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>onComplete</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>Optional action to be executed on the context element once the effect has been fully applied. Can be used to chain effects when combined with the <i>effect</i> key.</p></td></tr>
 *         </table>
 *         <p>+ any (other) keys supported by the <a target="_BLANK" href="http://mootools.net/docs/core/Fx/Fx.Morph">Fx.Morph</a> options dictionary.</p>
 *       </td>
 *     </tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>callInstanceMethod</p></th><td style="vertical-align: top;"><p>dictionary</p></td>
 *       <td>
 *         <p>Call a method on an object "stored in" the context element using the MooTools <a target="_BLANK" href="http://mootools.net/docs/core/Element/Element#Element:store">Element.store</a> function.</p>
 *         <p>Supported keys:</p>
 *         <table>
 *           <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description</th></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>instanceName</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Name which the instance is stored under.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>methodName</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Name of the method to call.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>arguments</p></th><td style="vertical-align: top;"><p>list</p></td><td><p>Arguments to be passed along with the method call.</p></td></tr>
 *         </table>
 *       </td>
 *     </tr>
 *   </table>
 * @see execute_json_actions
 */
function apply_json_actions(element, action_dict) {
    var dummy_div, fx_morph, instance, args_str, eval_str;
    
    if (action_dict.insertBefore !== undefined) {
        dummy_div = new Element('div', {
            'html' : action_dict.insertBefore
        });
        
        activate_elements(dummy_div);
        
        dummy_div.getChildren().each(function (child_element) {
            child_element.dispose();
            child_element.inject(element, 'before');
            execute_script_tags(child_element);
        });
        
        dummy_div.destroy();
    }
    
    if (action_dict.insertAfter !== undefined) {
        dummy_div = new Element('div', {
            'html' : action_dict.insertBefore
        });
        
        activate_elements(dummy_div);
        
        dummy_div.getChildren().reverse().each(function (child_element) {
            child_element.dispose();
            child_element.inject(element, 'after');
            execute_script_tags(child_element);
        });
        
        dummy_div.destroy();
    }
    
    if (action_dict.replaceHTML !== undefined) {
        dummy_div = new Element('div', {
            'html' : action_dict.replaceHTML
        });
        
        activate_elements(dummy_div);
        
        dummy_div.getChildren().each(function (child_element) {
            child_element.dispose();
            child_element.inject(element, 'before');
            execute_script_tags(child_element);
        });
        
        dummy_div.destroy();
        element.destroy();
    }
    else {
        if (action_dict.properties) {
            element.setProperties(action_dict.properties);
            
            if (action_dict.properties.html) {
                activate_elements(element);
                
                execute_script_tags(element);
            }
        }
        
        if (action_dict.style) {
            element.setStyles(action_dict.style);
        }
        
        if (action_dict.effect) {
            if (action_dict.effect.transitionType) {
                if (action_dict.effect.easeType) {
                    action_dict.effect.transition = Fx.Transitions[action_dict.effect.transitionType][action_dict.effect.easeType];
                }
                else {
                    action_dict.effect.transition = Fx.Transitions[action_dict.effect.transitionType].easeInOut;
                }
            }
            else {
                if (action_dict.effect.easeType) {
                    action_dict.effect.transition = Fx.Transitions.Sine[action_dict.effect.easeType];
                }
                else {
                    action_dict.effect.transition = Fx.Transitions.Sine.easeInOut;
                }
            }
            
            if (action_dict.effect.onComplete) {
                action_dict.effect.onComplete = apply_json_actions.bind(null, [element, action_dict.effect.onComplete]);
            }
            
            fx_morph = new Fx.Morph(element, action_dict.effect);
            
            fx_morph.start(action_dict.effect.properties);
        }
        
        if (action_dict.callInstanceMethod) {
            instance = element.retrieve(action_dict.callInstanceMethod.instanceName);
            
            if (!Browser.Engine.trident) {
                if (action_dict.callInstanceMethod.arguments) {
                    args = action_dict.callInstanceMethod.arguments;
                }
                else {
                    args = null;
                }
                
                instance[action_dict.callInstanceMethod.methodName].apply(instance, args);
            }
            else {
                if (action_dict.callInstanceMethod.arguments) {
                    args_str = action_dict.callInstanceMethod.arguments.map(function (item) {
                        return JSON.encode(item);
                    }).join(', ');
                }
                else {
                    args_str = '';
                }
                
                if (action_dict.callInstanceMethod.methodName.match(/^[a-zA-Z]\w+$/)) {  // Safety check
                    eval_str = 'instance.' + action_dict.callInstanceMethod.methodName + '(' + args_str + ')';
                    
                    eval(eval_str);
                }
            }
        }
    }
}

/**
 * <p>Execute the non-context-dependent actions in <i>json_data</i>; this is called in response to a JSON-action request with the received data structure as <i>json_data</i>.</p>
 *
 * @param {dictionary} json_data <p>Dictionary containing actions that are to be executed on the current page.</p>
 *   <p>Supported keys:</p>
 *   <table>
 *     <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description / action performed</th></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>sequence</p></th><td style="vertical-align: top;"><p>list</p></td><td><p>A list containing action dictionaries to be executed in the order in which they appear in the list. For each dictionary in the list, {@link execute_json_actions} will be called again, with the dictionary as <i>json_data</i> and the same <i>target_element</i> as before.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>googleAnalyticsPageview</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Dispatches a page view signal to Google Analytics using registered tracker.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>googleAnalyticsEvent</p></th><td style="vertical-align: top;"><p>dictionary</p></td>
 *       <td>
 *         <p>Dispatches an event signal to Google Analytics using registered tracker.</p>
 *         <p>Required keys:</p>
 *         <table>
 *           <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description</th></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>category</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Event category.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>action</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Event action/name.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>label</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Label/auxiliary information.</p></td></tr>
 *         </table>
 *       </td>
 *     </tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>stickywin</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>Open a Clientcide <a target="_BLANK" href="http://www.clientcide.com/docs/UI/StickyWin">StickyWin</a> with the dictionary given as value as initialization options.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>stickywinModal</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>Open a Clientcide <a target="_BLANK" href="http://www.clientcide.com/docs/UI/StickyWin.Modal">StickyWin.Modal</a> with the dictionary given as value as initialization options.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>title</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Update the page title shown in the browser's title bar to the specified string.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>historyEvents</p></th><td style="vertical-align: top;"><p>list</p></td>
 *       <td>
 *         <p>A list of dictionaries with history events to be registered with Harald Kirschner's <a target="_BLANK" href="http://digitarald.de/project/history-manager/">HistoryManager</a>. HistoryManager is based on the notion of <i>history modules</i>. Each history module tracks the history state of a single independent component on the page, and is charged with updating its corresponding part of the hash section of the URL as needed. History modules are identified by a short string name, called a "key" in HistoryManager's online documentation.</p><p>Dictionaries in this list are iterated in sequence, and in each dictionary every key must be a string that matches the name of a previously registered history module (history modules should be registered in your project's initialization script).</p>
 *         <p>The value of each history module key should itself be a dictionary, with only one key currently supported:</p>
 *         <table>
 *           <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description</th></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>setValue</p></th><td style="vertical-align: top;"><p>dictionary</p></td>
 *           <td>
 *             <p>Calls {@link HistoryManagerX.setValue} for the module, thereby registering a new history event. The value is a dictionary of arguments to be passed along with the method call.</p>
 *             <p>Supported keys:</p>
 *             <table>
 *               <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description</th></tr>
 *               <tr><th style="text-align: left; vertical-align: top;"><p>index</p></th><td style="vertical-align: top;"><p>integer</p></td><td><p>Index at which to store the value. In most cases this will be zero.</p></td></tr>
 *               <tr><th style="text-align: left; vertical-align: top;"><p>value</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>The value to store. This needs to be a string that uniquely identifies the state of the tracked component, and which can be parsed by the history module's <i>onMatch</i> method (see the <a target="_BLANK" href="http://digitarald.de/project/history-manager/">HistoryManager documentation</a>).</p></td></tr>
 *             </table>
 *           </td></tr>
 *         </table>
 *       </td>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>actionRequest</p></th><td style="vertical-align: top;"><p>dictionary</p></td>
 *       <td>
 *         <p>Dispatches a new JSON action request. The response is executed as global actions as soon as it is recieved.</p>
 *         <p>Supported keys:</p>
 *         <table>
 *           <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description</th></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>url</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>URL to which the request is sent.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>data</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>Query string data to pass along with the request.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>method</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Request method. "GET" or "POST". Defaults to "GET" if omitted.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>onSuccess</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>Action dictionary to execute once, and if, the request has successfully recieved and executed its response.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>spinoutSelectors</p></th><td style="vertical-align: top;"><p>list</p></td><td><p>A list of CSS selectors (strings) whose matching elements will be disabled and temporarily partially concealed until the request successfully completes or times out.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>spinoutAncestorSelectors</p></th><td style="vertical-align: top;"><p>list</p></td><td><p>A list of CSS selectors (strings) whose matching elements among <i>target_element</i>s ancestors will be disabled and temporarily partially concealed until the request successfully completes or times out.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>spinoutIDs</p></th><td style="vertical-align: top;"><p>list</p></td><td><p>A list of string IDs for which the matching elements will be disabled and temporarily partially concealed until the request successfully completes or times out.</p></td></tr>
 *         </table>
 *       </td>
 *     </tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>callGlobalFunction</p></th><td style="vertical-align: top;"><p>dictionary</p></td>
 *       <td>
 *         <p>Call a global (visible on the <i>window</i> object) function.</p>
 *         <p>Supported keys:</p>
 *         <table>
 *           <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description</th></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>functionName</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Name of the function to call.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>arguments</p></th><td style="vertical-align: top;"><p>list</p></td><td><p>Arguments to be passed along with the function call.</p></td></tr>
 *         </table>
 *       </td>
 *     </tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>openModalEditor</p></th><td style="vertical-align: top;"><p>dictionary</p></td>
 *       <td>
 *         <p>Opens the modal editor as a view of the object at <i>objectURL</i>.</p>
 *         <p>Supported keys:</p>
 *         <table>
 *           <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description</th></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>objectURL</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>URL of the Grok object which to view/edit in the modal editor.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>onClose</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>Action to execute when the modal editor is closed. This will trigger another call to {@link execute_json_actions} once the modal editor closes, with the provided dictionary as <i>json_data</i>. <i>target_element</i> will be empty.</p></td></tr>
 *         </table>
 *       </td>
 *     </tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>openModalEditor</p></th><td style="vertical-align: top;"><p>dictionary</p></td>
 *       <td>
 *         <p>Opens the message editor as a view of the object at <i>objectURL</i>.</p>
 *         <p>Supported keys:</p>
 *         <table>
 *           <tr><th>Key</th><th>Value&nbsp;type</th><th>Value content description</th></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>objectURL</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>URL of the Grok object which to view/edit in the message editor.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>apropos</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>A short string that will be displayed in the editor and is meant to identify the object in the context of which messages are being posted.</p></td></tr>
 *           <tr><th style="text-align: left; vertical-align: top;"><p>onClose</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>Action to execute when the message editor is closed. This will trigger another call to {@link execute_json_actions} once the message editor closes, with the provided dictionary as <i>json_data</i>. <i>target_element</i> will be empty.</p></td></tr>
 *         </table>
 *       </td>
 *     </tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>targetElement</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>A context-dependent action dictionary to be applied to <i>target_element</i>. See {@link apply_json_actions} for more info on and syntax specification of context-dependent action dictionaries.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>elementsID</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>A dictionary with element ID strings as keys and context-dependent action dictionaries as values. For each element whose ID matches a key, the corresponding dictionary will be applied to the element. See {@link apply_json_actions} for more info on and syntax specification of context-dependent action dictionaries.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>elementsSelector</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>A dictionary with CSS selectors as keys and context-dependent action dictionaries as values. For each element matching a selector, the corresponding dictionary will be applied to the element. See {@link apply_json_actions} for more info on and syntax specification of context-dependent action dictionaries.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>elementsParentSelector</p></th><td style="vertical-align: top;"><p>dictionary</p></td><td><p>A dictionary with CSS selectors as keys and context-dependent action dictionaries as values. For each element among <i>target_element</i>s ancestors that match a selector, the corresponding dictionary will be applied to the element. See {@link apply_json_actions} for more info on and syntax specification of context-dependent action dictionaries.</p></td></tr>
 *     <tr><th style="text-align: left; vertical-align: top;"><p>redirect</p></th><td style="vertical-align: top;"><p>string</p></td><td><p>Will redirect to the URL provided as value after execution of other actions.</p></td></tr>
 *   </table>
 * @param {dom-element} target_element <p>Element on the page that triggered the actions, if any (for example, if actions were triggered by clicking an element with target="json", this will contain the clicked element).</p>
 * @see apply_json_actions
 */
function execute_json_actions(json_data, target_element) {
    var obj, func, modal_editor, finished_callback, spinout_elements, eval_str, args_str;
    
    if ($defined(json_data.sequence)) {
        json_data.sequence.each(function (json_data_dict) {
            execute_json_actions(json_data_dict, target_element);
        });
    }
    
    if ($defined(json_data.googleAnalyticsPageview) && $defined(pageTracker)) {
        pageTracker._trackPageview(json_data.googleAnalyticsPageview);
    }
    
    if ($defined(json_data.googleAnalyticsEvent) && $defined(pageTracker)) {
        pageTracker._trackEvent(json_data.googleAnalyticsEvent.category, json_data.googleAnalyticsEvent.action, json_data.googleAnalyticsEvent.label);
    }
    
    if ($defined(json_data.stickywin)) {
        obj = new StickyWin(json_data.stickywin);
        obj.element.store('stickywin', obj);
        activate_elements(obj.element);
        obj.element.setStyle('position', 'fixed');
    }
    
    if ($defined(json_data.stickywinModal)) {
        obj = new StickyWin.Modal(json_data.stickywinModal);
        obj.element.store('stickywin', obj);
        activate_elements(obj.element);
        obj.element.setStyle('position', 'fixed');
        obj.mask.element.setStyle('position', 'fixed');
        
        $$('html')[0].setStyle('overflow', 'hidden');
        
        obj.mask.element.setStyles({
            'width'  : window.getSize().x + 'px',
            'height' : window.getSize().y + 'px'
        });
        
        obj.element.setStyles({
            'left' : (window.getSize().x - obj.element.getSize().x) / 2 + 'px',
            'top'  : (window.getSize().y - obj.element.getSize().y) / 2 + 'px'
        });
        
        obj.mask.addEvent('hide', function (event) {
            $$('html')[0].setStyle('overflow', null);
        });
    }
    
    if ($defined(json_data.title)) {
        document.title = json_data.title;
    }
    
    if ($defined(json_data.historyEvents)) {
        json_data.historyEvents.each(function (history_event_dict) {
            history_event_dict = $H(history_event_dict);
            
            history_event_dict.getKeys().each(function (module_name) {
                if ($defined(history_event_dict[module_name].setValue)) {
                    HistoryManager.setValue(module_name, history_event_dict[module_name].setValue.index, history_event_dict[module_name].setValue.value);
                }
                
                if ($defined(history_event_dict[module_name].setValues)) {
                    HistoryManager.setValues(module_name, history_event_dict[module_name].setValues.values);
                }
            })
        });
    }
    
    if ($defined(json_data.actionRequest)) {
        if ($defined(json_data.actionRequest.onSuccess)) {
            finished_callback = function () {
                execute_json_actions(json_data.actionRequest.onSuccess, target_element);
            };
        }
        else {
            finished_callback = $empty;
        }
        
        spinout_elements = [];
        
        if ($defined(json_data.actionRequest.spinoutSelectors)) {
            json_data.actionRequest.spinoutSelectors.each(function (selector) {
                spinout_elements.extend(Array($$(selector)).flatten());    // Type coercion to 'array' needed to circumvent bug in MooTools 1.3
            });
        }
        
        if ($defined(json_data.actionRequest.spinoutAncestorSelectors)) {
            json_data.actionRequest.spinoutAncestorSelectors.each(function (selector) {
                spinout_elements.extend(Array(target_element.getParents(selector)).flatten());    // Type coercion to 'array' needed to circumvent bug in MooTools 1.3
            });
        }
        
        if ($defined(json_data.actionRequest.spinoutIDs)) {
            json_data.actionRequest.spinoutIDs.each(function (id) {
                spinout_elements.push($(id));
            });
        }
        
        json_action_request(json_data.actionRequest.url, json_data.actionRequest.data, target_element, json_data.actionRequest.method || 'get', finished_callback, spinout_elements);
    }
    
    if ($defined(json_data.callGlobalFunction)) {
        if (!Browser.Engine.trident) {
            if (json_data.callGlobalFunction.arguments) {
                args = json_data.callGlobalFunction.arguments;
            }
            else {
                args = null;
            }
            
            window[json_data.callGlobalFunction.functionName].apply(window, args);
        }
        else {
            if (json_data.callGlobalFunction.arguments) {
                args_str = json_data.callGlobalFunction.arguments.map(function (item) {
                    return JSON.encode(item);
                }).join(', ');
            }
            else {
                args_str = '';
            }
            
            if (json_data.callGlobalFunction.functionName.match(/^[a-zA-Z]\w+$/)) {  // Safety check
                eval_str = 'window.' + json_data.callGlobalFunction.functionName + '(' + args_str + ')';
                
                eval(eval_str);
            }
        }
    }
    
    if ($defined(json_data.openModalEditor)) {
        modal_editor = new ModalEditor(json_data.openModalEditor.objectURL);
        
        if ($defined(json_data.openModalEditor.onClose)) {
            modal_editor.addEvent('close', function () {
                execute_json_actions(json_data.openModalEditor.onClose);
            });
        }
    }
    
    if ($defined(json_data.openMessageEditor)) {
        modal_editor = new MessageEditor(json_data.openMessageEditor.objectURL, json_data.openMessageEditor.apropos);
        
        if ($defined(json_data.openMessageEditor.onClose)) {
            modal_editor.addEvent('close', function () {
                execute_json_actions(json_data.openMessageEditor.onClose);
            });
        }
    }
    
    if ($defined(json_data.elementsID)) {
        new Hash(json_data.elementsID).getKeys().each(function (element_id) {
            if ($(element_id)) {
                apply_json_actions($(element_id), json_data.elementsID[element_id])
            }
        });
    }
    
    if ($defined(json_data.elementsSelector)) {
        new Hash(json_data.elementsSelector).getKeys().each(function (element_selector) {
            $$(element_selector).each(function (element) {
                apply_json_actions(element, json_data.elementsSelector[element_selector]);
            });
        });
    }
    
    if ($defined(json_data.targetElement)) {
        apply_json_actions(target_element, json_data.targetElement);
    }
    
    if ($defined(json_data.elementsParentSelector)) {
        new Hash(json_data.elementsParentSelector).getKeys().each(function (element_parent_selector) {
            if (target_element.getParents(element_parent_selector)) {
                target_element.getParents(element_parent_selector).each(function (element) {
                    apply_json_actions(element, json_data.elementsParentSelector[element_parent_selector]);
                });
            }
        });
    }
    
    if ($defined(json_data.redirect)) {
        window.location.replace(json_data.redirect);
    }
}

function open_modal(content, target_element) {
    var stickywin_modal;
    
    stickywin_modal = new StickyWin.Modal({
        content : content
    });
}

function json_action_request(url, data, context_element, method, finished_callback, spinout_elements) {
    var spinner_delays, show_spinner, show_cancel_dialog, stop_spinners, cancel_stickywin, uri;
    
    uri = url.toURI();
    Object.append(data, uri.getData());
    uri.setData({});
    url = uri.toString();
    
    stop_spinners = $$('body')[0].retrieve('stop_spinners_function');
    
    if (stop_spinners) {
        stop_spinners(true);
        
        $$('body')[0].store('stop_spinners_function', null);
    }
    
    spinner_delays = [];
    
    cancel_stickywin = null;
    
    method = method || 'get';
    
    if (spinout_elements === undefined) {
        spinout_elements = [];
    }
    
    if (context_element) {
        if (context_element.get('spinout_selectors')) {
            context_element.get('spinout_selectors').split(';').each(function (selector) {
                spinout_elements.extend(Array($$(selector)).flatten());
            });
        }
        
        if (context_element.get('spinout_ancestor_selectors')) {
            context_element.get('spinout_ancestor_selectors').split(';').each(function (selector) {
                spinout_elements.extend(Array(context_element.getParents(selector)).flatten()); // Type-force to array needed to workaround apparent bug in MooTools 1.3
            });
        }
        
        if (context_element.get('spinout_ids')) {
            context_element.get('spinout_ids').split(';').each(function (id) {
                spinout_elements.push($(id));
            });
        }
    }
    
    show_spinner = function (spinner) {
        spinner.show();
    };
    
    show_cancel_dialog = function () {
        var dummy_div;
        
        dummy_div = new Element('div');
        
        dummy_div.grab(new Element('div', {
            'class' : 'cancel_request_message',
            'text'  : window.cancel_request_message || 'Sidan håller på att laddas in...'
        }));
        
        dummy_div.grab(new Element('input', {
            'class' : 'cancel_request_button',
            'type'  : 'button',
            'value' : window.cancel_request_button_text || 'Avbryt'
        }));
        
        cancel_stickywin = new StickyWin({
            'content'        : dummy_div.get('html'),
            'className'      : 'cancel_request_dialog',
            'destroyOnClose' : true
        });
        
        cancel_stickywin.pin();
        
        cancel_stickywin.element.getElements('input.cancel_request_button')[0].addEvent('click', function (event) {
            stop_spinners(false);
        }.bind(this));
    };
    
    stop_spinners = function (noFx) {
        if (cancel_stickywin) {
            cancel_stickywin.hide();
            cancel_stickywin = null;
        }
        
        if (noFx === undefined) noFx = true;  // no fade fx by default since size of element underneath
                                              // may instantly change when json effects are executed
        
        spinout_elements.each(function (spinout_element) {
            spinout_element.retrieve('spinner').hide(noFx);
            
            spinner_delays.each(function (spinner_delay) {
                $clear(spinner_delay);
            });
        });
    };

    $$('body')[0].store('stop_spinners_function', stop_spinners);
    
    spinout_elements.each(function (spinout_element) {
        var spinner = new Spinner(spinout_element);
        
        spinout_element.store('spinner', spinner);
        
        spinner_delays.push(show_spinner.delay(0, this, spinner));
    }.bind(this));
    
    if (spinout_elements.length > 0) {
        spinner_delays.push(show_cancel_dialog.delay(4000, this));
    }
    
    new Request.JSON({
        'url'     : url,
        'data'    : data,
        'method'  : method,
        'noCache' : true,
        
        'onSuccess' : function (response_object, response_text) {
            stop_spinners();
            
            $$('body')[0].store('stop_spinners_function', null);
            
            if (response_object) {
                execute_json_actions(response_object, context_element);
            }
            else {
                execute_json_actions(response_text, context_element);
            }
            
            if (finished_callback) {
                finished_callback();
            }
        }
    }).send();
};

function activate_action_link(a_element, response_handler) {
    var href_uri, data, path_locations;
    
    a_element.addEvent('click', function (event) {
        event.stop();
        
        href_uri = new URI(a_element.get('href'));
        
        data = $H(href_uri.getData());
        
        if (a_element.get('data_from')) {
            data.extend($(a_element.get('data_from')).toQueryString().parseQueryString());
        }
        
        json_action_request(a_element.get('href'), data, a_element, 'get');
    });
}

function activate_action_form(form_element, response_handler) {
    var data, submit_button_name;
    
    form_element.addEvent('submit', function (event) {
        event.stop();
        
        data = form_element.toQueryString().parseQueryString();
        
        json_action_request(form_element.get('action'), data, form_element, form_element.get('method') || 'post');
    });
    
    form_element.getElements('input[type="submit"]').each(function (submit_button) {
        submit_button.addEvent('click', function (event) {
            event.stop();
            
            data = form_element.toQueryString().parseQueryString();
            
            if (submit_button.get('name')) {
                data[submit_button.get('name')] = submit_button.get('value');
            }
            
            json_action_request(form_element.get('action'), data, form_element, form_element.get('method') || 'post');
        }.bind(this));
    }.bind(this));
}

function activate_json_actions(element) {
    element.getElements('a[target="json"]').each(function (a_element) {
        activate_action_link(a_element, execute_json_actions);
    });
    
    element.getElements('form[target="json"]').each(function (form_element) {
        activate_action_form(form_element, execute_json_actions);
    });
    element.getElements('a[target="navigate"]').each(function (a_element) {
        activate_navigation_link(a_element, execute_json_actions);
    });
    element.getElements('form[target="navigate"]').each(function (form_element) {
        activate_navigation_form(form_element, execute_json_actions);
    });
}

function activate_modal_actions(element) {
    element.getElements('a[target="modal"]').each(function (a_element) {
        activate_action_link(a_element, open_modal);
    });    

    
    element.getElements('form[target="modal"]').each(function (form_element) {
        activate_action_form(form_element, open_modal);
    });
}

function activate_tooltips(element) {
    var tooltip_elements;
    
    tooltip_elements = [];
    
    element.getElements('*[tooltip_title]').each(function (tooltip_element) {
        tooltip_elements.push(tooltip_element);
    });
    
    element.getElements('*[tooltip_text]').each(function (tooltip_element) {
        if (!tooltip_elements.contains(tooltip_element)) {
            tooltip_elements.push(tooltip_element);
        }
    });
    
    element.getElements('*[tooltip_options]').each(function (tooltip_element) {
        if (!tooltip_elements.contains(tooltip_element)) {
            tooltip_elements.push(tooltip_element);
        }
    });
    
    element.getElements('dfn[alt]').each(function (tooltip_element) {
        if (!tooltip_elements.contains(tooltip_element)) {
            tooltip_elements.push(tooltip_element);
        }
    });
    
    tooltip_elements.each(function (tooltip_element) {
        var tooltip_options;
        
        if (tooltip_element.get('tooltip_options')) {
            tooltip_options = new Hash(JSON.decode('{' + tooltip_element.get('tooltip_options') + '}'));
        }
        else {
            tooltip_options = new Hash();
        }
        
        tooltip_options.set('title', 'text');
        
        if (tooltip_element.get('tooltip_text')) {
            tooltip_options.set('text', 'tooltip_text');
        }
        
        if (tooltip_element.get('tooltip_title')) {
            tooltip_options.set('title', 'tooltip_title');
        }
        
        if (tooltip_element.get('alt')) {
            tooltip_options.set('text', 'alt');
        }
        
        new Tips.Pointy(tooltip_element, tooltip_options.getClean());
    });
}

function activate_overtexts(element) {
    element.getElements('input[overtext]').each(function (overtext_field) {
        new OverText(overtext_field, {
            'textOverride' : overtext_field.get('overtext')
        });
    });
}

window.element_activators = [
    activate_json_actions,
    activate_modal_actions,
    activate_tooltips,
    activate_overtexts
];

function activate_elements(element) {
    window.element_activators.each(function (element_activator) {
        element_activator(element);
    });
}

function execute_script_tags(element) {
    if ((element.get('tag') === 'script') && (element.get('type') === 'text/javascript')) {
        eval(element.get('text'));
    }
    else {
        element.getElements('script[type="text/javascript"]').each(function (script_element) {
            eval(script_element.get('text'));
        });
    }
}

function disable_links(element) {
    element.getElements('a').each(function (a_element) {
        a_element.set('href', null);
    });
    
    element.getElements('form').each(function (form_element) {
        form_element.set('action', null);
    });
    
    element.getElements('*[onclick]').each(function (onclick_element) {
        onclick_element.set('onclick', null);
    });
};

window.addEvent('domready', function () {
    activate_elements($(document.body));
});


function activate_navigation_link(a_element, response_handler) {
    var href_uri, data, path_locations;
    
    a_element.addEvent('click', function (event) {
        event.stop();
        
        href_uri = new URI(a_element.get('href'));
        
        data = $H(href_uri.getData());
        
        if (a_element.get('data_from')) {
            data.extend($(a_element.get('data_from')).toQueryString().parseQueryString());
        }
        
        if (a_element.get('data_from')) {
            data.extend($(a_element.get('data_from')).toQueryString().parseQueryString());
        }
        var url = a_element.get('href');
        url = url.replace("?!","/navigateJson?!");
        
        var prevHistory = $('yh_history_check_id').retrieve('history','').replace('#!','').replace('?','-').replace(/&/g,'/')
        var hash = url.split("navigateJson")[1];
        hash = hash.replace("?","#");
        window.location.hash=hash;
        $('yh_history_check_id').store('history',hash);
        if ($('course_presentation_placeholder')===undefined || $('course_presentation_placeholder')===null){
            json_action_request(url+')*('+prevHistory+'(((showObject)))', {}, a_element, 'get');
        } else {
            json_action_request(url+')*('+prevHistory+'(((injectObject)))', {}, a_element, 'get');
        }
            
        /* Update address */
        
    });
}

function activate_navigation_form(form_element, response_handler) {
    var data, submit_button_name;
    
    form_element.addEvent('submit', function (event) {
        event.stop();
        data = form_element.toQueryString().parseQueryString();
        
        //if (submit_button.get('name')) {
        //    data[submit_button.get('name')] = submit_button.get('value');
        //}
        var hashField = window.location.hash
        if (hashField.indexOf('?')!=-1){
            hashField = hashField.split('?')[0]
        }
        var searchParam = form_element.toQueryString()

        //personlighetstyper
        var personality_types_search = ''
        for (i = 0; i < (searchParam.split('&').length); i++){
            if (searchParam.split('&')[i].indexOf('personality_type_')!=-1){
                personality_types_search+=searchParam.split('&')[i].split('personality_type_')[1].split('=')[0]+','
            }
        }
        if (personality_types_search != '')
            personality_types_search = '&intressetyper='+personality_types_search.substring(0, personality_types_search.length-1);

        var course_areas_search = ''
        for (i = 0; i < (searchParam.split('&').length); i++){
            if (searchParam.split('&')[i].indexOf('coursearea_')!=-1){
                course_areas_search+=searchParam.split('&')[i].split('coursearea_')[1].split('=')[0]+','
            }
        }
        if (course_areas_search != '')
            course_areas_search = '&utbildningsomraden='+course_areas_search.substring(0, course_areas_search.length-1);

        //länet
        county_search = '&lan='+data['county']

        //fritexsök
        var freetext_search = ''
        if (data['search_text']!=''){
            freetext_search = '&fritext='+data['search_text']
        }

        //program
        var program_search = ''
        if (data['program']!=''&&data['program']!==undefined){
            program_search = '&program='+data['program']
        }

        //specialinriktning 
        var specialization_search = ''
        if (data['specialization']!='' && data['specialization']!==undefined){
            specialization_search = '&specialisering='+data['specialization']
        }

        //reservplats reserve=reserve
        var reserve_search = ''
        if (data['reserve']!='' && data['reserve']!==undefined){
            specialization_search = '&restplats=on'
        }

        var adjacent_counties_search = ''
        if (data['adjacent_counties']!='' && data['adjacent_counties']!==undefined){
            adjacent_counties_search = '&angransande-lan=on';
        }
        if (form_element.get('action').indexOf('bannerMenuSearch')!=-1 || form_element.get('action').indexOf('frontpageSearch')!=-1 ) {
            hashField = '#!/sok'
            window.location.hash=hashField+'?'+freetext_search+county_search+adjacent_counties_search+program_search+reserve_search+specialization_search+personality_types_search+course_areas_search;
            var prevHist = $('yh_history_check_id').retrieve('history','')            
            $('yh_history_check_id').store('history',window.location.hash);
            //window.location.hash=hashField+'?'+freetext_search+'&lan=all'+personality_types_search;
            //var prevHist = $('yh_history_check_id').retrieve('history','')            
            //$('yh_history_check_id').store('history',window.location.hash);
        } else {
            if (window.location.hash.indexOf('#!/sok')!=-1 || window.location.hash.indexOf('#!/kalender')!=-1){
                window.location.hash=hashField+'?'+freetext_search+county_search+adjacent_counties_search+program_search+reserve_search+specialization_search+personality_types_search+course_areas_search;
                var prevHist = $('yh_history_check_id').retrieve('history','')            
                $('yh_history_check_id').store('history',window.location.hash);
            }
        }            
       

        json_action_request(form_element.get('action'), data, form_element, form_element.get('method') || 'post');
        /*
        event.stop();
        
        data = form_element.toQueryString().parseQueryString();
        var hashField = window.location.hash
        if (hashField.indexOf('?')!=-1){
            hashField = hashField.split('?')[0]
        }
        var searchParam = form_element.toQueryString()
        window.location.hash=hashField+searchParam;
        json_action_request(form_element.get('action'), data, form_element, form_element.get('method') || 'post');
        */
    });
    
    form_element.getElements('input[type="submit"]').each(function (submit_button) {
        submit_button.addEvent('click', function (event) {
            event.stop();
            
            data = form_element.toQueryString().parseQueryString();
            
            if (submit_button.get('name')) {
                data[submit_button.get('name')] = submit_button.get('value');
            }
            var hashField = window.location.hash
            if (hashField.indexOf('?')!=-1){
                hashField = hashField.split('?')[0]
            }
            var searchParam = form_element.toQueryString()

            //personlighetstyper
            var personality_types_search = ''
            for (i = 0; i < (searchParam.split('&').length); i++){
                if (searchParam.split('&')[i].indexOf('personality_type_')!=-1){
                    personality_types_search+=searchParam.split('&')[i].split('personality_type_')[1].split('=')[0]+','
                }
            }
            if (personality_types_search != '')
                personality_types_search = '&intressetyper='+personality_types_search.substring(0, personality_types_search.length-1);

            var course_areas_search = ''
            for (i = 0; i < (searchParam.split('&').length); i++){
                if (searchParam.split('&')[i].indexOf('coursearea_')!=-1){
                    course_areas_search+=searchParam.split('&')[i].split('coursearea_')[1].split('=')[0]+','
                }
            }
            if (course_areas_search != '')
                course_areas_search = '&utbildningsomraden='+course_areas_search.substring(0, course_areas_search.length-1);

            //länet
            county_search = '&lan='+data['county']

            //fritexsök
            var freetext_search = ''
            if (data['search_text']!=''){
                freetext_search = '&fritext='+data['search_text']
            }

            //program
            var program_search = ''
            if (data['program']!=''&&data['program']!==undefined){
                program_search = '&program='+data['program']
            }

            //specialinriktning 
            var specialization_search = ''
            if (data['specialization']!='' && data['specialization']!==undefined){
                specialization_search = '&specialisering='+data['specialization']
            }

            //reservplats reserve=reserve
            var reserve_search = ''
            if (data['reserve']!='' && data['reserve']!==undefined){
                specialization_search = '&restplats=on'
            }

            var adjacent_counties_search = ''
            if (data['adjacent_counties']!='' && data['adjacent_counties']!==undefined){
                adjacent_counties_search = '&angransande-lan=on';
            }
            if (form_element.get('action').indexOf('bannerMenuSearch')!=-1 || form_element.get('action').indexOf('frontpageSearch')!=-1 ) {
                hashField = '#!/sok'
                window.location.hash=hashField+'?'+freetext_search+county_search+adjacent_counties_search+program_search+reserve_search+specialization_search+personality_types_search+course_areas_search;
                var prevHist = $('yh_history_check_id').retrieve('history','')            
                $('yh_history_check_id').store('history',window.location.hash);
                //window.location.hash=hashField+'?'+freetext_search+'&lan=all'+personality_types_search;
                //var prevHist = $('yh_history_check_id').retrieve('history','')            
                //$('yh_history_check_id').store('history',window.location.hash);
            } else {
                if (window.location.hash.indexOf('#!/sok')!=-1 || window.location.hash.indexOf('#!/kalender')!=-1){
                    window.location.hash=hashField+'?'+freetext_search+county_search+adjacent_counties_search+program_search+reserve_search+specialization_search+personality_types_search+course_areas_search;
                    var prevHist = $('yh_history_check_id').retrieve('history','')            
                    $('yh_history_check_id').store('history',window.location.hash);
                }
            }            
           

            json_action_request(form_element.get('action'), data, form_element, form_element.get('method') || 'post');
        }.bind(this));
    }.bind(this));
}
