Reworked Registration Levels & Charges page including new "Drag to Sort" feature.
authorChuck Scott <cscott@gaslightmedia.com>
Tue, 27 Nov 2018 17:36:15 +0000 (12:36 -0500)
committerChuck Scott <cscott@gaslightmedia.com>
Tue, 27 Nov 2018 17:36:15 +0000 (12:36 -0500)
classes/data/dataRegClass.php [changed mode: 0644->0755]
classes/data/dataRegEvent.php
css/admin.css [changed mode: 0644->0755]
models/admin/ajax/levelsAndRatesEdit.php [new file with mode: 0755]
models/admin/registrations/events.php [changed mode: 0644->0755]
models/admin/registrations/events_configureEvent.php [changed mode: 0644->0755]
models/admin/registrations/events_dashboard.php
setup/validActions.php [changed mode: 0644->0755]
views/admin/registrations/eventEditLevels.html
views/admin/registrations/eventsDashboard.html

old mode 100644 (file)
new mode 100755 (executable)
index 164fea0..67b6d87
@@ -143,6 +143,14 @@ class GlmDataRegistrationsRegClass extends GlmDataAbstract
                 'use'       => 'a'
             ),
 
+            // Sort Order
+            'sort_order' => array (
+                'field'     => 'sort_order',
+                'type'      => 'integer',
+                'required'  => false,
+                'use'       => 'a'
+            )
+
         );
 
     }
index 76448b7..4f6667c 100755 (executable)
@@ -927,9 +927,6 @@ class GlmDataRegistrationsRegEvent extends GlmDataAbstract
             } // each reg_time
         } // non_time_specific
 
-        // Add Recurrence summary to event data
-//        $this->regEventData['recurrenceSummary'] = $recurrenceSummary;
-
         // If recurrences is not requested - dump them now
         if (!$withRecurData) {
             unset($this->regEventData['recurrences']);
@@ -938,7 +935,15 @@ class GlmDataRegistrationsRegEvent extends GlmDataAbstract
         // Restore status of extended data flag
         $this->postProcAddedEventData = $saveExtended;
 
-        // echo "<pre>".print_r($this->regEventData,1)."</pre>";
+        // If we have good event data and there's levels listed, sort by specified sort_order
+        if ($this->regEventData && $this->regEventData['reg_class'] && count($this->regEventData['reg_class']) > 0) {
+            uasort($this->regEventData['reg_class'], function($a, $b) {
+                if ($a['sort_order'] == $b['sort_order']) {
+                    return 0;
+                }
+                return ($a['sort_order'] < $b['sort_order']) ? -1 : 1;
+            });
+        }
 
         return $this->regEventData;
 
old mode 100644 (file)
new mode 100755 (executable)
index a61a14d..7eba174
     padding-bottom: .7em;
     padding-top: 1em;
 }
-.glm-class-label {
-    height: 1em;
+.glm-reg-submit {
+    padding: 1rem;
+    position: absolute;
+    bottom: .3rem;
+    right: .5rem;
+}
+.glm-reg-gray {
+    color: gray;
+}
+
+/* Admin Edit Levels and Charges Page */
+.UNKNOWN-level-edit-form { /* ????? */
+    display: none;
+}
+.reg-level-container {
+    margin-bottom: 1em;
+    background-color: white; 
+    padding: .25rem; 
+    border: 1px solid black; 
+    cursor: pointer;
 }
-.glm-class-header {
+.reg-level-display-container {
+    margin-bottom: 1em;
+}
+.reg-level-header {
     width: 99%;
     border-bottom-style: solid;
     padding-bottom: 1em;
     padding-top: 1em;
 }
-.glm-reg-submit {
-    padding: 1rem;
-    position: absolute;
-    bottom: .3rem;
-    right: .5rem;
+.reg-level-label {
+    height: 1em;
 }
-.class-edit-form {
+.reg-level-edit-container {
     display: none;
+    border: 1px solid black; 
+    padding: 1em; 
+    background-color: #f8ffff;
+    margin: 1em;
 }
-.class-rate-container {
-    margin-top: 1em;
-    margin-left: 2em;
+.reg-level-header {
 }
-.glm-reg-gray {
-    color: gray;
+.reg-level-label-container {
+    padding-bottom: .3em;
+}
+.reg-level-rates-container {
+    margin-left: 1em;
 }
 
+.reg-rate-label-container {
+    margin-bottom: .5em;
+}
+.reg-rate-edit-container {
+    display: none; 
+    border: 1px solid black; 
+    padding: 1em; 
+    background-color: #f8ffff;
+    margin: 1em;
+}
 
 
 /* Styles for cartSummary.html output */
diff --git a/models/admin/ajax/levelsAndRatesEdit.php b/models/admin/ajax/levelsAndRatesEdit.php
new file mode 100755 (executable)
index 0000000..0ebca6f
--- /dev/null
@@ -0,0 +1,413 @@
+<?php
+
+/**
+ * Gaslight Media Members Database
+ * Edit Registration Levels & Charges (rates)
+ *
+ * PHP version 5.5
+ *
+ * @category glmWordPressPlugin
+ * @package  glmMembersDatabase
+ * @author   Chuck Scott <cscott@gaslightmedia.com>
+ * @license  http://www.gaslightmedia.com Gaslightmedia
+ * @version  0.1
+ */
+
+// Load Members data abstract
+//require_once GLM_MEMBERS_REGISTRATIONS_PLUGIN_CLASS_PATH . '/data/dataAccount.php';
+
+/**
+ * This class performs the work of handling images passed to it via
+ * an AJAX call that goes through the WorPress AJAX Handler.
+ *
+ */
+class GlmMembersAdmin_ajax_levelsAndRatesEdit // extends GlmDataRegistrationsAccount
+{
+
+    /**
+     * WordPress Database Object
+     *
+     * @var $wpdb
+     * @access public
+     */
+    public $wpdb;
+    /**
+     * Plugin Configuration Data
+     *
+     * @var $config
+     * @access public
+     */
+    public $config;
+    /**
+     * Required input not provided flag
+     *
+     * @var $requiredNotProvided
+     * @access public
+     */
+    public $requiredNotProvided = false;
+
+    /*
+     * Constructor
+     *
+     * This contructor sets up this model. At this time that only includes
+     * storing away the WordPress data object.
+     *
+     * @return object Class object
+     *
+     */
+    public function __construct ($wpdb, $config)
+    {
+
+        // Save WordPress Database object
+        $this->wpdb = $wpdb;
+
+        // Save plugin configuration object
+        $this->config = $config;
+
+        // Run constructor for data class
+//        parent::__construct(false, false);
+
+    }
+
+    /*
+     * Perform Model Action
+     *
+     * This model checks to see if the creditials passed in are correct.
+     *
+     * This model action does not return, it simply does it's work then calls die();
+     *
+     * @param $actionData
+     *
+     * Echos JSON string as response and does not return
+     */
+    public function modelAction( $actionData = false )
+    {
+        // Setup response array
+        $response = array(
+            'status'    => false,
+            'msg'       => false
+        );
+
+        $option = filter_var( $_REQUEST['option'], FILTER_SANITIZE_STRING );
+
+        switch ( $option ) {
+
+            case 'sortLevels':
+
+                // Fields should be in correct order in the submitted array and keys should be sequential from 0
+                foreach ($_REQUEST['sortedIds'] as $sortKey=>$sortVal) {
+
+                    // Strip "field_" from field ID number
+                    $fieldId = preg_replace("/[^0-9]/", "", $sortVal);
+
+                    // Update the field with the key as the order field
+                    $res = $this->wpdb->update(
+                            GLM_MEMBERS_REGISTRATIONS_PLUGIN_DB_PREFIX.'reg_class',
+                            array( 'sort_order' => $sortKey ),
+                            array( 'id' => $fieldId ),
+                            array( '%d' ),
+                            array( '%d' )
+                            );
+
+                    if ($res !== false) {
+                        $response['status'] = true;
+                    } else {
+                        $response['msg'] = $this->wpdb->last_error;
+                    }
+                }
+
+                break;
+
+            case 'addLevel':
+            case 'updateLevel':
+
+                $request = filter_input_array(
+                    INPUT_POST,
+                    array(
+                        'eventId'   => array(
+                                            'filter'    => FILTER_VALIDATE_INT,
+                                            'options'   => array('min_range' => 1)
+                                        ),
+                        'levelId'   => array(
+                                            'filter'    => FILTER_VALIDATE_INT,
+                                            'options'   => array('min_range' => 1)
+                                        ),
+                        'name'      => FILTER_SANITIZE_STRING,
+                        'descr'     => FILTER_SANITIZE_STRING,
+                        'sort_order'=> array(
+                                            'filter'    => FILTER_VALIDATE_INT,
+                                            'options'   => array('min_range' => 1)
+                                        )
+                    )
+                );
+
+                $msg = '';
+
+                if ($option == 'addLevel') {
+                    if ($request['eventId'] === false)  {$msg .= "* Required Event ID not a positive integer.<br>";}
+                } else {
+                    if ($request['levelId'] === false)  {$msg .= "* Required Level ID not a positive integer.<br>";}
+                }
+
+                if (trim($request['name']) == '')   {$msg .= "* Level Name not provided<br>";}
+                if (trim($request['descr']) == '')  {$msg .= "* Description not provided<br>";}
+
+                if ($msg != '') {
+                    $response['msg'] = 'Please check the following...<p style="text-align: left;">'.$msg."</p>";
+                } else {
+
+                    // If we have good data
+                    if ($request && isset($request['eventId']) && ($request['eventId']-0) > 0) {
+
+                        if ($option == 'addLevel') {
+
+                            // Add the new level
+                            $res = $this->wpdb->insert(
+                                GLM_MEMBERS_REGISTRATIONS_PLUGIN_DB_PREFIX.'reg_class',
+                                array(
+                                    'reg_event'     => $request['eventId'],
+                                    'name'          => $request['name'],
+                                    'descr'         => $request['descr'],
+                                    'sort_order'    => $request['sort_order']
+                                ),
+                                array(
+                                    '%d',
+                                    '%s',
+                                    '%s',
+                                    '%d'
+                                )
+                            );
+
+                        } else {
+
+                            // Update level
+                            $res = $this->wpdb->update(
+                                GLM_MEMBERS_REGISTRATIONS_PLUGIN_DB_PREFIX.'reg_class',
+                                array(
+                                    'name'          => $request['name'],
+                                    'descr'         => $request['descr'],
+                                ),
+                                array( 'id' => $request['levelId'] ),
+                                array(
+                                    '%s',
+                                    '%s'
+                                ),
+                                array( '%d' )
+                            );
+
+                        }
+
+                        // If no error
+                        if ($res !== false) {
+                            $response['status'] = true;
+                            if ($option == 'addLevel') {
+                                $response['levelId']     = $this->wpdb->insert_id;
+                            }
+                            $response = array_merge($request, $response);       // Appends or replaces $response values into $request
+                        } else {
+                            $response['msg'] = $this->wpdb->last_error;
+                        }
+
+                    } else {
+                        $response['msg'] = 'Add failed. Please check your data and try again.';
+                    }
+
+                }
+
+                break;
+
+            case 'deleteLevel':
+
+                $levelId = filter_var( $_REQUEST['levelId'], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1)));
+
+                if ($levelId && ($levelId-0) > 0 ) {
+
+                    // Delete all rates associated with this level
+                    $this->wpdb->delete(
+                        GLM_MEMBERS_REGISTRATIONS_PLUGIN_DB_PREFIX.'reg_rate',
+                        array( 'reg_class' => $levelId )
+                    );
+                    $res = $this->wpdb->delete(
+                        GLM_MEMBERS_REGISTRATIONS_PLUGIN_DB_PREFIX.'reg_class',
+                        array( 'id' => $levelId )
+                    );
+
+                    // If no error
+                    if ($res) {
+                        $response['status'] = true;
+                    } else {
+                        $response['msg'] = $this->wpdb->last_error;
+                    }
+
+                }
+
+                break;
+
+            case 'addRate':
+            case 'updateRate':
+
+                $request = filter_input_array(
+                    INPUT_POST,
+                    array(
+                        'eventId'           => array(
+                                                    'filter'    => FILTER_VALIDATE_INT,
+                                                    'options'   => array('min_range' => 1)
+                                                ),
+                        'levelId'           => array(
+                                                    'filter'    => FILTER_VALIDATE_INT,
+                                                    'options'   => array('min_range' => 1)
+                                                ),
+                        'rateId'            => array(
+                                                    'filter'    => FILTER_VALIDATE_INT,
+                                                    'options'   => array('min_range' => 1)
+                                                ),
+                        'name'              => FILTER_SANITIZE_STRING,
+                        'start_days'        => array(
+                                                    'filter'    => FILTER_VALIDATE_INT,
+                                                    'options'   => array('min_range' => 1)
+                                                ),
+                        'end_days'          => array(
+                                                    'filter'    => FILTER_VALIDATE_INT,
+                                                    'options'   => array('min_range' => 0)
+                                                ),
+                        'base_rate'         => FILTER_VALIDATE_FLOAT,       // Note that PHP filters don't validate range for float
+                        'per_registrant'    => FILTER_VALIDATE_FLOAT,
+                        'registrant_credits'=> array(
+                                                    'filter'    => FILTER_VALIDATE_INT,
+                                                    'options'   => array('min_range' => 0)
+                                                )
+                    )
+                );
+
+                // Sanity check all the values
+                $msg = '';
+
+                if ($option == 'addRate') {
+                    if ($request['eventId'] === false)                  {$msg .= "* Required event ID not a positive integer.<br>";}
+                    if ($request['levelId'] === false)                  {$msg .= "* Required registraion Level ID not a positive integer.<br>";}
+                } else {
+                    if ($request['rateId'] === false)                  {$msg .= "* Required registraion Rate ID not a positive integer.<br>";}
+                }
+
+                if (trim($request['name']) == '')                   {$msg .= "* Rate Name not provided<br>";}
+                if ($request['start_days'] === false)               {$msg .= "* Start Days must be greater than 0.<br>";}
+                if ($request['end_days'] === false)                 {$msg .= "* End Days must not be negative.<br>";}
+                if ($request['start_days'] < $request['end_days'])  {$msg .= "* Start days must be greater (earlier) than end days.<br>";}
+                if ($request['base_rate'] < 0)                      {$msg .= "* Bate Rate must not be negative.<br>";}
+                if ($request['per_registrant'] < 0)                 {$msg .= "* Rate Per Registrant must not be negative.<br>";}
+                if ($request['registrant_credits'] === false)       {$msg .= "* Required Registrant Credits must not be negative.<br>";}
+
+
+                if ($msg != '') {
+                    $response['msg'] = 'Please check the following...<p style="text-align: left;">'.$msg."</p>";
+                } else {
+
+                    // If we have good data
+                    if ($request && isset($request['eventId']) && ($request['eventId']-0) > 0) {
+
+                        if ($option == 'addRate') {
+
+                            // Add the new rate
+                            $res = $this->wpdb->insert(
+                                GLM_MEMBERS_REGISTRATIONS_PLUGIN_DB_PREFIX.'reg_rate',
+                                array(
+                                    'reg_event'             => $request['eventId'],
+                                    'reg_class'             => $request['levelId'],
+                                    'name'                  => $request['name'],
+                                    'start_days'            => $request['start_days'],
+                                    'end_days'              => $request['end_days'],
+                                    'base_rate'             => $request['base_rate'],
+                                    'per_registrant'        => $request['per_registrant'],
+                                    'registrant_credits'    => $request['registrant_credits']
+                                ),
+                                array(
+                                    '%d',   // Event ID
+                                    '%d',   // Level ID
+                                    '%s',   // Name
+                                    '%d',   // Start Days
+                                    '%d',   // End Days
+                                    '%d',   // Base Rate
+                                    '%f',   // Per Registrant
+                                    '%d'     // Registrant Credits
+                                )
+                            );
+
+                        } else {
+
+                            // Update rate
+                            $res = $this->wpdb->update(
+                                GLM_MEMBERS_REGISTRATIONS_PLUGIN_DB_PREFIX.'reg_rate',
+                                array(
+                                    'name'                  => $request['name'],
+                                    'start_days'            => $request['start_days'],
+                                    'end_days'              => $request['end_days'],
+                                    'base_rate'             => $request['base_rate'],
+                                    'per_registrant'        => $request['per_registrant'],
+                                    'registrant_credits'    => $request['registrant_credits']
+                                ),
+                                array( 'id' => $request['rateId'] ),
+                                array(
+                                    '%s',   // Name
+                                    '%d',   // Start Days
+                                    '%d',   // End Days
+                                    '%f',   // Base Rate
+                                    '%f',   // Per Registrant
+                                    '%d'    // Registrant Credits
+                                ),
+                                array( '%d' )
+                            );
+
+                        }
+
+                        // If no error
+                        if ($res !== false) {
+                            $response['status'] = true;
+                            if ($option == 'addRate') {
+                                $response['rateId']     = $this->wpdb->insert_id;
+                            }
+                            $response = array_merge($request, $response);   // Appends or replaces $response values into $request
+                        } else {
+                            $response['msg'] = $this->wpdb->last_error;
+                        }
+
+                    } else {
+                        $response['msg'] = 'Add failed. Please check your data and try again.';
+                    }
+
+                }
+
+                break;
+
+            case 'deleteRate':
+
+                $rateId = filter_var( $_REQUEST['rateId'], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1)));
+
+                if ($rateId && ($rateId-0) > 0 ) {
+
+                    // Delete all rates associated with this level
+                    $res = $this->wpdb->delete(
+                            GLM_MEMBERS_REGISTRATIONS_PLUGIN_DB_PREFIX.'reg_rate',
+                            array( 'id' => $rateId )
+                            );
+
+                    // If no error
+                    if ($res !== false) {
+                        $response['status'] = true;
+                    } else {
+                        $response['msg'] = $this->wpdb->last_error;
+                    }
+
+                }
+
+                break;
+
+        }
+
+        if ($response) {
+            header('Content-type:application/json;charset=utf-8', true);
+            echo json_encode( $response );
+        }
+        wp_die();
+    }
+
+}
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
index 347fde1..a78e4c6
  */
 
 $regEvent                  = false;
-$regEventJSON              = false;
-$regClassesJSON            = false;
-$regTimesJSON              = false;
 $regEventUpdated           = false;
 $regEventUpdateError       = false;
 
-$scripts = array(
-    'backbone-local' => 'js/lib/backbone.localStorage.min.js',
-    'regApp'         => 'js/adminRegApp.js',
-);
-foreach ( $scripts as $scriptName => $scriptPath ) {
-    wp_register_script(
-        $scriptName,
-        GLM_MEMBERS_REGISTRATIONS_PLUGIN_URL . $scriptPath,
-        array( 'jquery', 'backbone', 'underscore' ),
-        '1.0',
-        true
-        );
-}
-wp_enqueue_script( array( 'jquery', 'backbone' ) );
-wp_enqueue_script( array_keys( $scripts ) );
-$regEvent = array();
-
-// Get all current registration event data
-$regEvent = $this->getEventConfig($regEventID, false, false, true);
-
-// Make all arrays of arrays non-associative to make Backbone happy
-if (is_array($regEvent['reg_class'])) {
-    foreach ($regEvent['reg_class'] as $k=>$v) {
-        if (is_array($regEvent['reg_class'][$k]['reg_rate'])) {
-            $regEvent['reg_class'][$k]['reg_rate'] = array_values($regEvent['reg_class'][$k]['reg_rate']);
-        }
-    }
+// Get Event ID and if an integer try to get the event data
+$regEventId = filter_input(INPUT_GET, 'regEventID', FILTER_VALIDATE_INT);
+if ($regEventId) {
+    $regEvent = $this->getEventConfig($regEventId, false, false, true);
 }
-if (is_array($regEvent['reg_class'])) {
-    $regEvent['reg_class'] = array_values($regEvent['reg_class']);
-    $regClassesJSON = json_encode($regEvent['reg_class']);
-}
-if (is_array($regEvent['reg_time'])) {
-    $regEvent['reg_time'] = array_values($regEvent['reg_time']);
-    $regTimesJSON = json_encode($regEvent['reg_time']);
-}
-
-// Get rid of class and time arrays so we just have the event data
-unset($regEvent['reg_class']);
-unset($regEvent['reg_time']);
-$regEventJSON = json_encode($regEvent);
-
 
+//echo "<pre>".print_r($regEvent,1)."</pre>";exit;
 $templateData = array(
     'regEvent'                => $regEvent,
-    'regEventJSON'            => $regEventJSON,
-    'regClassesJSON'          => $regClassesJSON,
-    'regTimesJSON'            => $regTimesJSON,
     'regEventUpdated'         => $regEventUpdated,
     'regEventUpdateError'     => $regEventUpdateError
 );
index c905d25..5601a7c 100755 (executable)
@@ -33,8 +33,6 @@ $alphaWhere         = '';
 $where              = ' TRUE ';
 $haveRequests       = false;
 
-
-
 // If doing alpha list
 if (isset($_REQUEST['alpha'])) {
     $actionData['request']['alpha'] = $_REQUEST['alpha'];
@@ -58,6 +56,24 @@ if (isset($_REQUEST['text_search']) && trim($_REQUEST['text_search'] != '')) {
     $textSearch = stripslashes($textSearch);
 }
 
+// Check if we're doing paging
+if ( isset( $_REQUEST['pageSelect'] ) ) {
+
+    // If request is for Next
+    if ( $_REQUEST['pageSelect'][0] == 'N' ) {
+        $newStart = $_REQUEST['nextStart'] - 0;
+
+        // Otherwise it must be Previous
+    } else {
+        $newStart = $_REQUEST['prevStart'] - 0;
+    }
+
+    if ($newStart > 0) {
+        $start = $newStart;
+    }
+
+}
+
 // Get full list for all other filters, but not filtered by alpha (that would be silly)
 $alphaList = $this->getAlphaList('');
 
old mode 100644 (file)
new mode 100755 (executable)
index 14cae63..88b1646
@@ -70,7 +70,8 @@ $glmMembersRegistrationsAddOnValidActions = array(
             'setupEventRegQueue'        => GLM_MEMBERS_REGISTRATIONS_PLUGIN_SLUG,
             'runEventRegQueue'          => GLM_MEMBERS_REGISTRATIONS_PLUGIN_SLUG,
             'getAuthorizeNetSeal'       => GLM_MEMBERS_REGISTRATIONS_PLUGIN_SLUG,
-            'regPayPal'                 => GLM_MEMBERS_REGISTRATIONS_PLUGIN_SLUG
+            'regPayPal'                 => GLM_MEMBERS_REGISTRATIONS_PLUGIN_SLUG,
+            'levelsAndRatesEdit'        => GLM_MEMBERS_REGISTRATIONS_PLUGIN_SLUG
         ),
         'registrations' => array(
             'index'                     => GLM_MEMBERS_REGISTRATIONS_PLUGIN_SLUG,
index 8bb9699..54966b8 100755 (executable)
@@ -1,76 +1,73 @@
 {include file='admin/registrations/eventHeader.html'}
 {include file='admin/registrations/eventSubTabs.html'}
 
-    <h1>
-        {if $regEventUpdated}<span class="glm-notice glm-flash-updated">{$terms.reg_term_registration_cap} {$terms.reg_term_event_cap} Updated</span>{/if}
-        {if $regEventUpdateError}<span class="glm-error glm-flash-updated">{$terms.reg_term_registration_cap} {$terms.reg_term_event_cap} Update Error</span>{/if}
-        {if $regEventAdded}<span class="glm-notice glm-flash-updated">{$terms.reg_term_registration_cap} {$terms.reg_term_event_cap} Added</span>{/if}
-    </h1>
-
-    {* Event Registration App - Backbone.js *}
-    {* Underscore Templates for the Event Registration App *}
-
-    {literal}
-
-        <script type="text/template" id="regEvent-template">
-                <a id="class-add" class="button button-secondary glm-button glm-right">Add a {/literal}{$terms.reg_term_registration_cap}{literal} Level</a>
-                <h1>{/literal}{$terms.reg_term_registration_cap}{literal} Levels & Charges</h1>
-        </script>
-
-        <script type="text/template" id="regClass-template">
-            <div style="background-color: white; padding: .25rem; border: 1px solid black;">
-                <div class="class-display-template">
-                    <div class="glm-class-header">
-                        <div class="glm-right">
-                            <a class="class-edit button button-secondary glm-button-small">Edit</a>
-                            <a class="class-delete button button-secondary glm-button-small">Delete</a>
-                            <a class="class-add-rate button button-secondary glm-button-small">Add Rate</a>
-                        </div>
-                        <div class="glm-class-label">
-                            <h3><%= name %></h3>
-                        </div>
-                    </div>
-                    <%= descr %>
-                </div>
-                <div class="class-edit-template" style="display: none;">
-                    <div class="glm-class-header">
-                        <div class="glm-right">
-                            <a class="class-cancel button glm-button-small">Cancel</a>
-                            <a class="class-update button glm-button-small-highlighted">Update</a>
-                            <a class="class-add button glm-button-small-highlighted" style="display: none;">Add</a>
-                            <a class="class-delete button button-secondary glm-button-small">Delete</a>
-                        </div>
-                        <div class="glm-class-label" style="padding-bottom: .3em;">
-                            <b>Level Name:</b> <input class="class-name" type="text" name="name_<% if (id) { %><%- id %><% } else { %> new <% } %>" value="<% if (name) { %><%- name %><% } %>">
-                        </div>
-                    </div>
-                    <b>Description:</b> <input class="class-descr" type="text" name="reg_descr_<% if (id) { %><%- id %><% } else { %> new <% } %>" size="90" value="<% if (descr) { %><%- descr %><% } %>">
-                </div>
-                <div class="class-rate-container">
-                </div>
+<h1>
+    {if $regEventUpdated}<span class="glm-notice glm-flash-updated">{$terms.reg_term_registration_cap} {$terms.reg_term_event_cap} Updated</span>{/if}
+    {if $regEventUpdateError}<span class="glm-error glm-flash-updated">{$terms.reg_term_registration_cap} {$terms.reg_term_event_cap} Update Error</span>{/if}
+    {if $regEventAdded}<span class="glm-notice glm-flash-updated">{$terms.reg_term_registration_cap} {$terms.reg_term_event_cap} Added</span>{/if}
+</h1>
+
+<a class="reg-level-add-button button button-secondary glm-button glm-right">Add a {$terms.reg_term_registration_cap} {$terms.reg_term_level_cap}</a>
+<h1>{$terms.reg_term_registration_cap} {$terms.reg_term_level_plur_cap} & Charges</h1>
+<p><b class="glm-notice">DRAG TO SORT:</b> The order in which {$terms.reg_term_level_plur} are displayed on the front-end is the same as the order they appear below. Drag a {$terms.reg_term_level} up or down to set the desired order.</p>
+
+<div id="regLevelsContainer" title="Drag me up or down to sort {$terms.reg_term_registration} {$terms.reg_term_level_plur}!">
+{if $regEvent.reg_class}
+  {foreach $regEvent.reg_class as $level}
+
+<!-- Registration Level -->
+<div id="regLevelContainer_{$level.id}" class="reg-level-container ">
+    <div id="regLevelDisplayContainer_{$level.id}" class="reg-level-display-container">
+        <div class="reg-level-header">
+            <div class="glm-right">
+                <a data-option="editLevel" data-id="{$level.id}" class="reg-level-edit-button button button-secondary glm-button-small">Edit</a>
+                <a data-option="deleteLevel" data-id="{$level.id}" class="reg-level-delete-button button button-secondary glm-button-small">Delete</a>
+                <a data-option="addRate" data-id="{$level.id}" class="reg-level-add-rate-button button button-secondary glm-button-small">Add Rate</a>
+            </div>
+            <div class="reg-level-label">
+                <h3 id="regLevelName_{$level.id}">{$level.name}</h3>
+            </div>
+        </div>
+        <div id="regLevelDescr_{$level.id}">{$level.descr}</div>
+    </div>
+    <div id="regLevelEditContainer_{$level.id}" class="reg-level-edit-container">
+        <div class="reg-level-header">
+            <div class="glm-right">
+                <a data-option="cancelEditLevel" data-id="{$level.id}" class="reg-level-edit-cancel-button button glm-button-small">Cancel</a>
+                <a data-option="updateEditLevel" data-id="{$level.id}" class="reg-level-edit-update-button button glm-button-small-highlighted">Update</a>
             </div>
-        </script>
+            <b class="glm-required">{$terms.reg_term_level_cap} Name:</b> <input id="levelName_{$level.id}" class="level-name" type="text" name="name_{$level.id}" value="{$level.name}">
+        </div>
+        <b class="glm-required">Description:</b> <input id="levelDescr_{$level.id}" class="level-descr" type="text" name="reg_descr_{$level.id}" size="90" value="{$level.descr}">
+        <p class="glm-required">Required fields are in red.</p>
+    </div>
+    <div id="regLevelRatesContainer_{$level.id}" class="reg-level-rates-container">
+  {if $level.reg_rate}
+    {foreach $level.reg_rate as $rate}
 
-        <script type="text/template" id="regRate-template">
-            <div class="rate-display-template">
+        <!-- Registration Level Rate -->
+        <div id="regRateContainer_{$rate.id}">
+            <div id="regRateDisplayContainer_{$rate.id}" class="rate-display">
                 <div class="glm-rate-header">
                     <div class="glm-right">
-                        <a class="rate-edit button button-secondary glm-button-small">Edit</a>
-                        <a class="rate-delete button button-secondary glm-button-small">Delete Rate</a>
+                        <a data-option="editRate" data-id="{$rate.id}" data-level="{$level.id}" class="reg-rate-edit-button button button-secondary glm-button-small">Edit</a>
+                        <a data-option="deleteRate" data-id="{$rate.id}" data-level="{$level.id}" class="reg-rate-delete-button button button-secondary glm-button-small">Delete Rate</a>
                     </div>
-                    <div class="glm-rate-label" style="margin-bottom: .5em;">
-                        <h3><%= name %></h3>
-                        Start Days: <%= start_days %>, End Days: <%= end_days %>, Base: $<%= base_rate %>. Per-Registrant: $<%= per_registrant %>. Registrant Credits: <%= registrant_credits %>
+                    <div class="reg-rate-label-container">
+                        <h3 id="regRateName_{$rate.id}">{$rate.name}</h3>
+                        Start Days: <span id="regRateStartDays_{$rate.id}">{$rate.start_days}</span>,
+                        End Days: <span id="regRateEndDays_{$rate.id}">{$rate.end_days}</span>,
+                        Base: <span id="regRateBaseRate_{$rate.id}">{$rate.base_rate_money}</span>.
+                        Per-Registrant: <span id="regRatePerRegistrant_{$rate.id}">{$rate.per_registrant_money}</span>.
+                        Registrant Credits: <span id="regRateRegistrantCredits_{$rate.id}">{$rate.registrant_credits}</span>
                     </div>
                 </div>
             </div>
-            <div class="rate-edit-template" style="display: none; border: 1px solid black; padding: 1em; background-color: #f8ffff;">
+            <div id="regRateEditContainer_{$rate.id}" class="reg-rate-edit-container">
                 <div class="glm-rate-header">
                     <div class="glm-right">
-                        <a class="rate-cancel button glm-button-small">Cancel</a>
-                        <a class="rate-update button glm-button-small-highlighted">Update</a>
-                        <a class="rate-add button glm-button-small-highlighted" style="display: none;">Add</a>
-                        <a class="rate-delete button button-secondary glm-button-small">Delete</a>
+                        <a data-option="cancelEditRate" data-id="{$rate.id}" class="reg-rate-edit-cancel-button button glm-button-small">Cancel</a>
+                        <a data-option="updateEditRate" data-id="{$rate.id}" class="reg-rate-edit-update-button button glm-button-small-highlighted">Update</a>
                     </div>
                     <h3>Rate Days and Costs</h3>
                     <div class="glm-rate-label" style="padding-bottom: .3em;">
                                 <th></th>
                                 <td>
                                     <p>
-                                        {/literal}
                                         "Rates" are a schedule of what is paid to {$terms.reg_term_register} for an {$terms.reg_term_event}. Rates can change based on the number of days until the {$terms.reg_term_event}.
                                         For example, an earier {$terms.reg_term_registration} may be less expensive than a last minute {$terms.reg_term_registration}.
                                         The start and end days for a rate must not overlap that of another rate for the same {$terms.reg_term_registration} level.
                                         The earliest "Start Days" determines the earliest date on which {$terms.reg_term_registration} is available and the last "End Days" closest to the {$terms.reg_term_event} determines the last date on which {$terms.reg_term_registration} is available.
-                                        {literal}
                                     </p>
                                 </td>
                             </tr>
+                            <tr><td>&nbsp;</td><td class="glm-required">Required fields are in red.</td></tr>
                             <tr>
-                                <th style="white-space: nowrap;">Rate Name:</th>
+                                <th style="white-space: nowrap;" class="glm-required">Rate Name:</th>
                                 <td>
-                                    <input class="rate-name glm-form-text-input" type="text" name="name_<% if (id) { %><%- id %><% } else { %> new <% } %>" value="<% if (name) { %><%- name %><% } %>">
+                                    <input id="rateName_{$rate.id}" class="rate-name glm-form-text-input" type="text" name="name_{$rate.id}" value="{$rate.name}">
                                 </td>
                             </tr>
                             <tr>
-                                <th style="white-space: nowrap;">Start Days</th>
+                                <th style="white-space: nowrap;" class="glm-required">Start Days</th>
                                 <td>
-                                    <input class="rate-start-days glm-form-text-input-veryshort" type="text" name="start_days_<% if (id) { %><%- id %><% } else { %> new <% } %>" value="<% if (start_days) { %><%- start_days %><% } %>">
-                                    Number of days before {/literal}{$terms.reg_term_event}{literal} that this rate starts. Must not overlap other rates.
+                                    <input id="rateStartDays_{$rate.id}" class="rate-start-days glm-form-text-input-veryshort" type="text" name="start_days_{$rate.id}" value="{$rate.start_days}">
+                                    Number of days before {$terms.reg_term_event} that this rate starts. Must not overlap other rates.
                                 </td>
                             </tr>
                             <tr>
-                                <th style="white-space: nowrap;">End Days:</th>
+                                <th style="white-space: nowrap;" class="glm-required">End Days:</th>
                                 <td>
-                                    <input class="rate-end-days glm-form-text-input-veryshort" type="text" name="end_days_<% if (id) { %><%- id %><% } else { %> new <% } %>" value="<% if (end_days) { %><%- end_days %><% } %>">
-                                    Number of days before {/literal}{$terms.reg_term_event}{literal} that this rate ends. Must not overlap other rates.
+                                    <input id="rateEndDays_{$rate.id}" class="rate-end-days glm-form-text-input-veryshort" type="text" name="end_days_{$rate.id}" value="{$rate.end_days}">
+                                    Number of days before {$terms.reg_term_event} that this rate ends. Must not overlap other rates.
                                 </td>
                             </tr>
                             <tr>
                                 <th style="white-space: nowrap;">Cost:</th>
                                 <td>
-                                    <span class="glm-nowrap">Base $<input class="rate-base-rate glm-form-text-input-veryshort" type="text" name="base_rate_<% if (id) { %><%- id %><% } else { %> new <% } %>" value="<% if (base_rate) { %><%- base_rate %><% } %>"></span>
-                                    <span class="glm-nowrap">Per-Registrant $<input class="rate-per-registrant glm-form-text-input-veryshort" type="text" name="per_registrant_<% if (id) { %><%- id %><% } else { %> new <% } %>" value="<% if (per_registrant) { %><%- per_registrant %><% } %>"></span>
-                                    <span class="glm-nowrap">Registrant Credits <input class="rate-registrant-credits glm-form-text-input-veryshort" type="text" name="registrant_credits_<% if (id) { %><%- id %><% } else { %> new <% } %>" value="<% if (registrant_credits) { %><%= registrant_credits %><% } %>"></span>
+                                    <span class="glm-nowrap"><span class="glm-required"><b>Base Rate</b></span> $<input id="rateBaseRate_{$rate.id}" class="rate-base-rate glm-form-text-input-veryshort" type="text" name="base_rate_{$rate.id}" value="{$rate.base_rate|string_format:"%.2f"}"></span>&nbsp;&nbsp;
+                                    <span class="glm-nowrap"><span class="glm-required"><b>Rate Per-Registrant</b></span> $<input id="ratePerRegistrant_{$rate.id}" class="rate-per-registrant glm-form-text-input-veryshort" type="text" name="per_registrant_{$rate.id}" value="{$rate.per_registrant|string_format:"%.2f"}"></span>&nbsp;&nbsp;
+                                    <span class="glm-nowrap"><span class="glm-required"><b>Registrants included in Base Rate</b></span> <input id="rateRegistrantCredits_{$rate.id}" class="rate-registrant-credits glm-form-text-input-veryshort" type="text" name="registrant_credits_{$rate.id}" value="{$rate.registrant_credits}"></span>
                                     <br>
                                 </td>
                             </tr>
                     </div>
                 </div>
             </div>
-        </script>
+        </div>
+
+    {/foreach} {* Rate *}
+  {/if}
+    </div> <!-- Rates Container -->
 
-    {/literal}
+</div> <!-- Level Container -->
 
-    <div class="glm-reg-event-list" id="regApp">
+  {/foreach}  {* Reg Level *}
+{/if}
+</div>
+
+<div id="regLevelTemplate" class="glm-hidden">
+    <div id="regLevelContainer_{ id }" class="reg-level-container ">
+        <div id="regLevelDisplayContainer_{ id }" class="reg-level-display-container">
+            <div class="reg-level-header">
+                <div class="glm-right ">
+                    <a data-option="editLevel" data-id="{ id }" class="reg-level-edit-button button button-secondary glm-button-small">Edit</a>
+                    <a data-option="deleteLevel" data-id="{ id }" class="reg-level-delete-button button button-secondary glm-button-small">Delete</a>
+                    <a data-option="addRate" data-id="{ id }" class="reg-level-add-rate-button button button-secondary glm-button-small">Add Rate</a>
+                </div>
+                <div class="reg-level-label">
+                    <h3 id="regLevelName_{ id }">{ pendingName }</h3>
+                </div>
+            </div>
+            <div id="regLevelDescr_{ id }">{ pendingDescr }</div>
+        </div>
+        <div id="regLevelEditContainer_{ id }" class="reg-level-edit-container">
+            <div class="reg-level-header">
+                <div class="glm-right routedEvent">
+                    <a data-option="cancelEditLevel" data-id="{ id }" class="reg-level-edit-cancel-button button glm-button-small">Cancel</a>
+                    <a data-option="updateEditLevel" data-id="{ id }" class="reg-level-edit-update-button button glm-button-small-highlighted">Update</a>
+                </div>
+                <b class="glm-required">{$terms.reg_term_level_cap} Name:</b> <input id="levelName_{ id }" class="level-name" type="text" name="name_{ id }" value="{ pendingName }">
+            </div>
+            <b class="glm-required">Description:</b> <input id="levelDescr_{ id }" class="level-descr" type="text" name="reg_descr_{ id }" size="90" value="{ pendingDescr }">
+            <p class="glm-required">Required fields are in red.</p>
+        </div>
+        <div id="regLevelRatesContainer_{ id }" class="reg-level-rates-container"></div>
     </div>
+</div>
 
-    {* Bootstrap the models needed on page load *}
-    {* Need to have RegEvent model created *}
-    {* And create the RegClasses collection *}
-    <script>
-
-        // Start with submit not required as 0, this is incremented for each edit area opened
-        var glmSubmitRequired = 0;
-
-        //var $=jQuery.noConflict();
-        var ajaxUrl = '{$ajaxUrl}?action=glm_members_admin_ajax';
-        var app = {
-            Models: { Admin: {} },
-            Collections: { Admin: {} },
-            Views: { Admin: {} },
-        };
-        var regEvent = '';
-
-        jQuery(function($){
-            regEvent = new app.Models.Admin.RegEvent;
-            regEvent.setClasses({$regClassesJSON});
-//            regEvent.setTimes({$regTimesJSON});
-            regEvent.set( {$regEventJSON} );
-            new app.Views.Admin.EventEditLevels();
-
-            // If submit is required and we're laving the page, alert the user - Note, there is no way to change the pop-up message anymore.
-            $(window).bind('beforeunload', function() {
-                if (glmSubmitRequired) {
-                    return true;
-                }
-            });
+<div id="regRateTemplate" class="glm-hidden">
+    <div id="regRateContainer_{ id }">
+        <div id="regRateDisplayContainer_{ id }" class="rate-display">
+            <div class="glm-rate-header">
+                <div class="glm-right">
+                    <a data-option="editRate" data-id="{ id }" data-level="{ levelId }" class="reg-rate-edit-button button button-secondary glm-button-small">Edit</a>
+                    <a data-option="deleteRate" data-id="{ id }" data-level="{ levelId }" class="reg-rate-delete-button button button-secondary glm-button-small">Delete Rate</a>
+                </div>
+                <div class="reg-rate-label-container">
+                    <h3 id="regRateName_{ id }">{ pendingRateName }</h3>
+                    Start Days: <span id="regRateStartDays_{ id }">{ pendingRateStartDays }</span>,
+                    End Days: <span id="regRateEndDays_{ id }">{ pendingRateEndDays }</span>,
+                    Base: <span id="regRateBaseRate_{ id }">${ pendingRateBaseRate }</span>.
+                    Per-Registrant: <span id="regRatePerRegistrant_{ id }">${ pendingRatePerRegistrant }</span>,
+                    Registrant Credits: <span id="regRateRegistrantCredits_{ id }">{ pendingRateRegistrantCredits }</span>
+                </div>
+            </div>
+        </div>
+        <div id="regRateEditContainer_{ id }" class="reg-rate-edit-container">
+            <div class="glm-rate-header">
+                <div class="glm-right">
+                    <a data-option="cancelEditRate" data-id="{ id }" class="reg-rate-edit-cancel-button button glm-button-small">Cancel</a>
+                    <a data-option="updateEditRate" data-id="{ id }" class="reg-rate-edit-update-button button glm-button-small-highlighted">Update</a>
+                </div>
+                <h3>Rate Days and Costs</h3>
+                <div class="glm-rate-label" style="padding-bottom: .3em;">
+                    <table class="glm-admin-table">
+                        <tr>
+                            <th></th>
+                            <td>
+                                <p>
+                                    "Rates" are a schedule of what is paid to {$terms.reg_term_register} for an {$terms.reg_term_event}. Rates can change based on the number of days until the {$terms.reg_term_event}.
+                                    For example, an earier {$terms.reg_term_registration} may be less expensive than a last minute {$terms.reg_term_registration}.
+                                    The start and end days for a rate must not overlap that of another rate for the same {$terms.reg_term_registration} level.
+                                    The earliest "Start Days" determines the earliest date on which {$terms.reg_term_registration} is available and the last "End Days" closest to the {$terms.reg_term_event} determines the last date on which {$terms.reg_term_registration} is available.
+                                </p>
+                            </td>
+                        </tr>
+                        <tr><td>&nbsp;</td><td class="glm-required">Required fields are in red.</td></tr>
+                        <tr>
+                            <th style="white-space: nowrap;" class="glm-required">Rate Name:</th>
+                            <td>
+                                <input id="rateName_{ id }" class="rate-name glm-form-text-input" type="text" name="name_{ id }" value="{ pendingRateName }">
+                            </td>
+                        </tr>
+                        <tr>
+                            <th style="white-space: nowrap;" class="glm-required">Start Days</th>
+                            <td>
+                                <input id="rateStartDays_{ id }" class="rate-start-days glm-form-text-input-veryshort" type="text" name="start_days_{ id }" value="{ pendingRateStartDays }">
+                                Number of days before {$terms.reg_term_event} that this rate starts. Must not overlap other rates.
+                            </td>
+                        </tr>
+                        <tr>
+                            <th style="white-space: nowrap;" class="glm-required">End Days:</th>
+                            <td>
+                                <input id="rateEndDays_{ id }" class="rate-end-days glm-form-text-input-veryshort" type="text" name="end_days_{ id }" value="{ pendingRateEndDays }">
+                                Number of days before {$terms.reg_term_event} that this rate ends. Must not overlap other rates.
+                            </td>
+                        </tr>
+                        <tr>
+                            <th style="white-space: nowrap;">Cost:</th>
+                            <td>
+                                <span class="glm-nowrap"><span class="glm-required"><b>Base Rate</b></span> $<input id="rateBaseRate_{ id }" class="rate-base-rate glm-form-text-input-veryshort" type="text" name="base_rate_{ id }" value="{ pendingRateBaseRate }"></span>&nbsp;&nbsp;
+                                <span class="glm-nowrap"><span class="glm-required"><b>Rate Per-Registrant</b></span> $<input id="ratePerRegistrant_{ id }" class="rate-per-registrant glm-form-text-input-veryshort" type="text" name="per_registrant_{ id }" value="{ pendingRatePerRegistrant }"></span>&nbsp;&nbsp;
+                                <span class="glm-nowrap"><span class="glm-required"><b>Registrants included in Base Rate</b></span> <input id="rateRegistrantCredits_{ id }" class="rate-registrant-credits glm-form-text-input-veryshort" type="text" name="registrant_credits_{ id }" value="{ pendingRateRegistrantCredits }"></span>
+                                <br>
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div id="msgDialog" style="text-align: center; font-weight: bold; padding-top: 1.5em;"></div>
+
+<script type="text/javascript">
+    jQuery(document).ready(function($) {
+
+        var editActive = false;
+        var levelEditId = false;
+        var levelId = false;
+        var rateId = false;
+        var newEdit = false;
+        var rateEditId = false;
+        var newItemSortVal = 9000; // Increment for each new item to ensure sane default ordering
+
+        var failMsg = 'Sorry, we could not communicate with our server. Your changes may not have been saved.';
+
+        /*
+         * Operations using deligated events
+         */
+        $("#regLevelsContainer").on("click", "a", function(event) {
+            event.stopPropagation();
+            var eventOption = $(this).data('option');
+            switch (eventOption) {
+
+                case 'editLevel':
+
+                    if (!checkEditActive()) {
+
+                        levelEditId = $(this).data('id');
+                        editActive = true;
+
+                        $('#regLevelDisplayContainer_' + levelEditId).hide();
+                        $('#regLevelEditContainer_' + levelEditId).show();
+
+                        disableSorting();
+                    }
+
+                    break;
+
+                case 'cancelEditLevel':
+
+                    if (editActive && levelEditId) {
+
+                        // If this is a new level
+                        if (levelEditId == 'newId') {
+                            $('#regLevelContainer_newId').remove();
+                        } else {
+                            $('#regLevelDisplayContainer_' + levelEditId).show();
+                            $('#regLevelEditContainer_' + levelEditId).hide();
+                        }
+                        levelEditId = false;
+                        editActive = false;
+
+                        enableSorting();
+
+                    }
+
+                    break;
+
+                case 'updateEditLevel':
+
+                    // Build request to update level
+                    var formData = {
+                        'action':       'glm_members_admin_ajax',
+                        'glm_action':   'levelsAndRatesEdit',
+                        'option':       'updateLevel',
+                        'eventId':      {$regEvent.id},
+                        'levelId':      levelEditId,
+                        'name':         $('#levelName_' + levelEditId).val(),
+                        'descr':        $('#levelDescr_' + levelEditId).val()
+                    };
+                    if (newEdit) {
+                        formData.option = 'addLevel';
+                        formData.sort_order = newItemSortVal++;
+                    }
+
+                    updatingDialog();
+
+                    // Send update via AJAX
+                    $.ajax({
+                        type:       'POST',
+                        url:        '{$ajaxUrl}',
+                        data:       formData,
+                        encode:     true,
+                        dataType:   'json'
+                    })
+                    .done(function(data) {
+
+                        updatingDialogClose();
+
+                        // If success
+                        if (data.status) {
+
+
+                            if (newEdit) {
+
+                                // Drop temp newId container and create a new one with the correct ID and data
+                                $('#regLevelContainer_' + levelEditId ).remove();
+
+                                // Get level template
+                                var levelTemplate = $('#regLevelTemplate').html();
+
+                                // Replace the ID with new ID
+                                levelTemplate = levelTemplate.replace(/{ id }/g, data.levelId);
+                                levelTemplate = levelTemplate.replace(/{ pendingName }/g, data.name);
+                                levelTemplate = levelTemplate.replace(/{ pendingDescr }/g, data.descr);
+
+                                // Append to level
+                                $('#regLevelsContainer').append(levelTemplate);
+
+                                // Show the new Level display and hide edit
+                                $('#regLevelDisplayContainer_' + data.levelId).show();
+                                $('#regLevelEditContainer_' + data.levelId).hide();
+
+                            } else {
+
+                                // Set the display data to the new values
+                                $('#regLevelName_' + levelEditId).html(data.name);
+                                $('#regLevelDescr_' + levelEditId).html(data.descr);
+
+                                // Now show the display container and hide the edit container
+                                $('#regLevelDisplayContainer_' + levelEditId).show();
+                                $('#regLevelEditContainer_' + levelEditId).hide();
+
+                            }
+
+                            levelEditId = false;
+                            editActive = false;
+                            newEdit = false;
+
+                            enableSorting();
+
+                        } else {
+                            openMsgDialog(data.msg);
+                        }
+                    })
+                    .fail( function() {
+                        updatingDialogClose();
+                        alert(failMsg);
+                    });
+
+                    break;
+
+                case 'deleteLevel':
+
+                    levelId = $(this).data('id');
+                    if (!checkEditActive() && levelId && confirm('Are you sure you want to delete this {$terms.reg_term_level} and all rates under it?')) {
+
+                        // Send delete request via AJAX
+                        var formData = {
+                            'action':       'glm_members_admin_ajax',
+                            'glm_action':   'levelsAndRatesEdit',
+                            'option':       'deleteLevel',
+                            'levelId':      $(this).data('id')
+                        };
+
+                        updatingDialog();
+
+                        $.ajax({
+                            type:       'POST',
+                            url:        '{$ajaxUrl}',
+                            data:       formData,
+                            encode:     true,
+                            dataType:   'JSON'
+                        })
+                        .done(function(data) {
+
+                            updatingDialogClose();
+
+                            // if success
+                            if (data.status) {
+                                var deleted = true;
+
+                                if (deleted) {
+                                    $('#regLevelContainer_' + levelId ).remove();
+                                }
+                            }
+                        })
+                        .fail( function() {
+                            updatingDialogClose();
+                            alert(failMsg);
+                        });
+
+                    }
+
+                    break;
+
+                // Setup to enter a new rate
+                case 'addRate':
+
+                    if (!checkEditActive()) {
+
+                        rateEditId = 'newId';
+                        editActive = true;
+                        newEdit = true;
+
+                        // Get Level for this rate
+                        levelId = $(this).data('id');
+
+                        // Get rate template
+                        var rateTemplate = $('#regRateTemplate').html();
+
+                        // Replace the ID with 'newId'
+                        rateTemplate = rateTemplate.replace(/{ id }/g, 'newId');
+
+                        // Initialize all data fields
+                        rateTemplate = rateTemplate.replace(/{ pendingRateName }/g, '');
+                        rateTemplate = rateTemplate.replace(/{ pendingRateStartDays }/g, '0');
+                        rateTemplate = rateTemplate.replace(/{ pendingRateEndDays }/g, '0');
+                        rateTemplate = rateTemplate.replace(/{ pendingRateBaseRate }/g, '0.00');
+                        rateTemplate = rateTemplate.replace(/{ pendingRatePerRegistrant }/g, '0.00');
+                        rateTemplate = rateTemplate.replace(/{ pendingRateRegistrantCredits }/g, '0');
+
+                        // Append to rates in rate's level
+                        $('#regLevelRatesContainer_' + levelId).append(rateTemplate);
+
+                        $('#regRateDisplayContainer_newId').hide();
+                        $('#regRateEditContainer_newId').show();
+
+                        disableSorting();
+
+                    }
+
+                    break;
+
+                // Setup to edit an existing rate
+                case 'editRate':
+
+                    if (!checkEditActive()) {
+
+                        rateEditId = $(this).data('id');
+                        editActive = true;
+
+                        // Get Level for this rate
+                        levelId = $(this).data('level');
+
+                        $('#regRateDisplayContainer_' + rateEditId).hide();
+                        $('#regRateEditContainer_' + rateEditId).show();
+
+                        disableSorting();
+
+                    }
+
+                    break;
+
+                // Update or Insert Rate
+                case 'updateEditRate':
+
+                    // Build request to update rate
+                    var formData = {
+                        'action':               'glm_members_admin_ajax',
+                        'glm_action':              'levelsAndRatesEdit',
+                        'option':               'updateRate',
+                        'eventId':              {$regEvent.id},
+                        'levelId':              levelId,
+                        'rateId':               rateEditId,
+                        'name':                 $('#rateName_' + rateEditId).val(),
+                        'start_days':           $('#rateStartDays_' + rateEditId).val(),
+                        'end_days':             $('#rateEndDays_' + rateEditId).val(),
+                        'base_rate':            $('#rateBaseRate_' + rateEditId).val(),
+                        'per_registrant':       $('#ratePerRegistrant_' + rateEditId).val(),
+                        'registrant_credits':   $('#rateRegistrantCredits_' + rateEditId).val()
+                    };
+
+                    if (newEdit) {
+                        formData.option = 'addRate';
+                        formData.sort_order = newItemSortVal++;
+                    }
+
+                    updatingDialog();
+
+                    // Send update via AJAX
+                    $.ajax({
+                        type:       'POST',
+                        url:        '{$ajaxUrl}',
+                        data:       formData,
+                        encode:     true,
+                        dataType:   'json'
+                    })
+                    .done(function(data) {
+
+                        updatingDialogClose();
+
+                        // If success
+                        if (data.status) {
+
+                            if (newEdit) {
+
+                                // Drop temp newId container and create a new one with the correct ID and data
+                                $('#regRateContainer_' + rateEditId ).remove();
+
+                                // Get Rate template
+                                var rateTemplate = $('#regRateTemplate').html();
+
+                                // Set parameters into the form template copy
+                                rateTemplate = rateTemplate.replace(/{ id }/g, data.rateId);
+                                rateTemplate = rateTemplate.replace(/{ levelId }/g, data.levelId);
+                                rateTemplate = rateTemplate.replace(/{ pendingRateName }/g, data.name);
+                                rateTemplate = rateTemplate.replace(/{ pendingRateStartDays }/g, data.start_days);
+                                rateTemplate = rateTemplate.replace(/{ pendingRateEndDays }/g, data.end_days);
+                                rateTemplate = rateTemplate.replace(/{ pendingRateBaseRate }/g, data.base_rate.toFixed(2));
+                                rateTemplate = rateTemplate.replace(/{ pendingRatePerRegistrant }/g, data.per_registrant.toFixed(2));
+                                rateTemplate = rateTemplate.replace(/{ pendingRateRegistrantCredits }/g, data.registrant_credits);
+
+                                // Append to rate
+                                $('#regLevelRatesContainer_' + data.levelId).append(rateTemplate);
+
+                                // Show the new rate display area and hide new edit area
+                                $('#regRateDisplayContainer_' + data.rateId).show();
+                                $('#regRateEditContainer_' + data.rateId).hide();
+
+                            } else {
+
+                                // Update the displayed rate data
+                                $('#regRateName_' + rateEditId).html(data.name),
+                                $('#regRateStartDays_' + rateEditId).html(data.start_days),
+                                $('#regRateEndDays_' + rateEditId).html(data.end_days),
+                                $('#regRateBaseRate_' + rateEditId).html('$' + data.base_rate.toFixed(2)),
+                                $('#regRatePerRegistrant_' + rateEditId).html('$' + data.per_registrant.toFixed(2)),
+                                $('#regRateRegistrantCredits_' + rateEditId).html(data.registrant_credits)
+
+                                // Show display and hide edit
+                                $('#regRateDisplayContainer_' + rateEditId).show();
+                                $('#regRateEditContainer_' + rateEditId).hide();
+
+                            }
+
+                            rateEditId = false;
+                            editActive = false;
+                            newEdit = false;
+
+                            enableSorting();
+
+                        } else {
+                            openMsgDialog(data.msg);
+                        }
+                    })
+                    .fail( function() {
+                        updatingDialogClose();
+                        alert(failMsg);
+                    });
+
+                    break;
+
+                case 'cancelEditRate':
+
+                    if (editActive && rateEditId) {
+
+                        // If this is a new rate
+                        if (editActive && rateEditId && newEdit) {
+                            $('#regRateContainer_newId').remove();
+                        } else {
+                            $('#regRateDisplayContainer_' + rateEditId).show();
+                            $('#regRateEditContainer_' + rateEditId).hide();
+                        }
+                        rateEditId = false;
+                        editActive = false;
+
+                        enableSorting();
+
+                    }
+
+                    break;
+
+                case 'deleteRate':
+
+                    rateId = $(this).data('id');
+                    if (!checkEditActive() && rateId && confirm('Are you sure you want to delete this rate?')) {
+
+                        // Send delete request via AJAX
+                        var formData = {
+                            'action':       'glm_members_admin_ajax',
+                            'glm_action':   'levelsAndRatesEdit',
+                            'option':       'deleteRate',
+                            'rateId':      $(this).data('id')
+                        };
+
+                        updatingDialog();
+
+                        $.ajax({
+                            type:       'POST',
+                            url:        '{$ajaxUrl}',
+                            data:       formData,
+                            encode:     true,
+                            dataType:   'JSON'
+                        })
+                        .done(function(data) {
+
+                            updatingDialogClose();
+
+                            // if success
+                            if (data.status) {
+                                var deleted = true;
+
+                                if (deleted) {
+                                    $('#regRateContainer_' + rateId ).remove();
+                                }
+                            }
+                        })
+                        .fail( function() {
+                            updatingDialogClose();
+                            alert(failMsg);
+                        });
+
+                    }
+
+                    break;
+
+            }
 
         });
-    </script>
 
-</div>
+        /*
+         * Operations not using delegated events
+         */
+
+        // Level dragable
+        $('#regLevelsContainer').sortable({
+            items: '.reg-level-container',
+            update: function(event, ui) {
+
+                // Create an array of the fields in the current order by field ID (field_{ id })
+                var sortedIds = $(this).sortable('toArray');
+
+                updatingDialog();
+
+                // Send new order via AJAX
+                var formData = {
+                    'action':       'glm_members_admin_ajax',
+                    'glm_action':   'levelsAndRatesEdit',
+                    'option':       'sortLevels',
+                    'sortedIds':    sortedIds
+                };
+
+                updatingDialog();
+
+                $.ajax({
+                    type:       'POST',
+                    url:        '{$ajaxUrl}',
+                    data:       formData,
+                    encode:     true,
+                    dataType:   'json'
+                })
+                .done( function(data) {
+
+                    updatingDialogClose();
+
+                    if (data.status == false) {
+                        var msg = 'An error occurred attempting to save the new level order.';
+                        if (data.msg) {
+                        msg += data.msg;
+                        }
+                        alert(msg);
+                    }
+                })
+                .fail( function() {
+                    updatingDialogClose();
+                    alert(failMsg);
+                })
+            }
+
+        });
+
+        // Add Level
+        $('.reg-level-add-button').on('click', function() {
+
+            if (!checkEditActive()) {
+
+                levelEditId = 'newId';
+                editActive = true;
+                newEdit = true;
+
+                // Get level template
+                var levelTemplate = $('#regLevelTemplate').html();
+
+                // Replace the ID with 'newId'
+                levelTemplate = levelTemplate.replace(/{ id }/g, 'newId');
+
+             // Initialize all data fields
+                levelTemplate = levelTemplate.replace(/{ pendingName }/g, '');
+                levelTemplate = levelTemplate.replace(/{ pendingDescr }/g, '');
+
+                // Append to level
+                $('#regLevelsContainer').append(levelTemplate);
+
+                $('#regLevelDisplayContainer_newId').hide();
+                $('#regLevelEditContainer_newId').show();
+
+                disableSorting();
+            }
+//            updatingDialogClose();
+        });
+
+        function disableSorting() {
+            $('#regLevelsContainer').sortable('disable');
+            $('.reg-level-container').css('cursor', 'default');
+        }
+        function enableSorting() {
+            $('#regLevelsContainer').sortable('enable');
+            $('.reg-level-container').css('cursor', 'pointer');
+        }
+
+        function checkEditActive() {
+            if (editActive) {
+                openMsgDialog('Please finish your current edit or update form first.');
+            }
+            return editActive;
+        }
+        function openMsgDialog(msg) {
+            $('#msgDialog').dialog({
+                modal: true,
+                resizable: false,
+                dragable: false,
+                closeOnEscape: true,
+                width: 'auto'
+            });
+            $('#msgDialog').html(msg);
+        }
+        function updatingDialog() {
+            openMsgDialog('UPDATING: Please wait!');
+        }
+        function updatingDialogClose() {
+            $('#msgDialog').dialog('close');
+        }
+
+    });
+</script>
 
 {include file='admin/footer.html'}
 
index 0bad13f..c8d5d52 100755 (executable)
@@ -19,7 +19,6 @@
             <input type="submit" value="Submit" style="margin-right: 2em;">
         </div>
 
-    </form>
 
     <p><b>Total found:</b> {$regEventsCount}&nbsp;&nbsp;</p>
 
     </table>
 
     {if $paging}
-        <input type="Submit" name="pageSelect" value="Previous {$limit} {$terms.reg_term_event_plur_cap}" class="button button-secondary glm-button"{if !$prevStart} disabled{/if}>
-        <input type="Submit" name="pageSelect" value="Next {$limit} {$terms.reg_term_event_plur_cap}" class="button button-secondary glm-button"{if !$nextStart} disabled{/if}>
+        <input type="Submit" name="pageSelect" value="Previous {$limit} {$terms.reg_term_registration_cap} {$terms.reg_term_event_plur_cap}" class="button button-secondary glm-button"{if !$prevStart} disabled{/if}>
+        <input type="Submit" name="pageSelect" value="Next {$limit} {$terms.reg_term_registration_cap} {$terms.reg_term_event_plur_cap}" class="button button-secondary glm-button"{if !$nextStart} disabled{/if}>
     {/if}
+    </form>
 
 </div>