Commits

Matthew Schinckel  committed 6caf1a4

Handling for new-style awards structure

  • Participants
  • Parent commits 4cd8ed4

Comments (0)

Files changed (4)

File jsonfield/static/js/form2object.js

+/**
+ * Copyright (c) 2010 Maxim Vasiliev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author Maxim Vasiliev
+ * Date: 09.09.2010
+ * Time: 19:02:33
+ */
+
+(function()
+{
+	/**
+	 * Returns form values represented as Javascript object
+	 * "name" attribute defines structure of resulting object
+	 *
+	 * @param rootNode {Element|String} root form element (or it's id)
+	 * @param delimiter {String} structure parts delimiter defaults to '.'
+	 * @param skipEmpty {Boolean} should skip empty text values, defaults to true
+	 */
+	window.form2object = function(rootNode, delimiter, skipEmpty)
+	{
+		if (typeof skipEmpty == 'undefined' || skipEmpty == null) skipEmpty = true;
+		if (typeof delimiter == 'undefined' || delimiter == null) delimiter = '.';
+		rootNode = typeof rootNode == 'string' ? document.getElementById(rootNode) : rootNode;
+
+		var formValues = getFormValues(rootNode);
+		var result = {};
+		var arrays = {};
+
+		for (var i = 0; i < formValues.length; i++)
+		{
+			var value = formValues[i].value;
+			if (skipEmpty && value === '') continue;
+
+			var name = formValues[i].name;
+			var nameParts = name.split(delimiter);
+
+			var currResult = result;
+
+			for (var j = 0; j < nameParts.length; j++)
+			{
+				var namePart = nameParts[j];
+
+				var arrName;
+
+				if (namePart.indexOf('[]') > -1 && j == nameParts.length - 1)
+				{
+					arrName = namePart.substr(0, namePart.indexOf('['));
+
+					if (!currResult[arrName]) currResult[arrName] = [];
+					currResult[arrName].push(value);
+				}
+				else
+				{
+					if (namePart.indexOf('[') > -1)
+					{
+						arrName = namePart.substr(0, namePart.indexOf('['));
+						var arrIdx = namePart.replace(/^[a-z]+\[|\]$/gi, '');
+
+						/*
+						 * Because arrIdx in field name can be not zero-based and step can be
+						 * other than 1, we can't use them in target array directly.
+						 * Instead we're making a hash where key is arrIdx and value is a reference to
+						 * added array element
+						 */
+
+						if (!arrays[arrName]) arrays[arrName] = {};
+						if (!currResult[arrName]) currResult[arrName] = [];
+
+						if (j == nameParts.length - 1)
+						{
+							currResult[arrName].push(value);
+						}
+						else
+						{
+							if (!arrays[arrName][arrIdx])
+							{
+								currResult[arrName].push({});
+								arrays[arrName][arrIdx] = currResult[arrName][currResult[arrName].length - 1];
+							}
+						}
+
+						currResult = arrays[arrName][arrIdx];
+					}
+					else
+					{
+						if (j < nameParts.length - 1) /* Not the last part of name - means object */
+						{
+							if (!currResult[namePart]) currResult[namePart] = {};
+							currResult = currResult[namePart];
+						}
+						else
+						{
+							currResult[namePart] = value;
+						}
+					}
+				}
+			}
+		}
+
+		return result;
+	};
+
+	function getFormValues(rootNode)
+	{
+		var result = [];
+		var currentNode = rootNode.firstChild;
+
+		while (currentNode)
+		{
+			if (currentNode.nodeName.match(/INPUT|SELECT|TEXTAREA/i))
+			{
+				var fieldValue = getFieldValue(currentNode);
+				if (fieldValue !== null) result.push({ name: currentNode.name, value: fieldValue});
+			}
+			else
+			{
+				var subresult = getFormValues(currentNode);
+				result = result.concat(subresult);
+			}
+
+			currentNode = currentNode.nextSibling;
+		}
+
+		return result;
+	}
+
+	function getFieldValue(fieldNode)
+	{
+		switch (fieldNode.nodeName) {
+			case 'INPUT':
+			case 'TEXTAREA':
+				switch (fieldNode.type.toLowerCase()) {
+					case 'radio':
+					case 'checkbox':
+						if (fieldNode.checked) return fieldNode.value;
+						break;
+
+					case 'button':
+					case 'reset':
+					case 'submit':
+					case 'image':
+						return '';
+						break;
+
+					default:
+						return fieldNode.value;
+						break;
+				}
+				break;
+
+			case 'SELECT':
+				return getSelectedOptionValue(fieldNode);
+				break;
+
+			default:
+				break;
+		}
+
+		return null;
+	}
+
+	function getSelectedOptionValue(selectNode)
+	{
+		var multiple = selectNode.multiple;
+		if (!multiple) return selectNode.value;
+
+		var result = [];
+		for (var options = selectNode.getElementsByTagName("option"), i = 0, l = options.length; i < l; i++)
+		{
+			if (options[i].selected) result.push(options[i].value);
+		}
+
+		return result;
+	}
+
+	/**
+	 * @deprecated Use form2object() instead
+	 * @param rootNode
+	 * @param delimiter
+	 */
+	window.form2json = window.form2object;
+
+})();

File jsonfield/static/js/json-table-templates.js

 $.template('table',
-    "<table border='1' label='{{= label }}' class='json-data-table'>                     " +
+    "<table border='1' class='json-data-table'>                     " +
     "    <tr class='header-row'>                                                         " +
     "        <th>                                                                        " +
-    "            {{= label }}                                                            " +
     "        </th>                                                                       " +
     "        {{each(i, column) headers.columnHeaders}}                                   " +
     "            {{tmpl({column:column, column_id:i}) 'column-header'}}                  " +
     "        </th>                                                                       " +
     "    </tr>                                                                           " +
     "    {{each(i, row) headers.rowHeaders}}                                             " +
-    "        {{tmpl({row:row, headers:headers, data:data, label:label, row_id:i}) 'row'}}" +
+    "        {{tmpl({row:row, headers:headers, data:data, row_id:i}) 'row'}}" +
     "    {{/each}}                                                                       " +
     "    <tr><th><button class='add-row'>Add {{= defaults.rowName }}</button></th></tr>  " +
     "</table>                                                                            "
     '        {{tmpl({row:row, row_id:row_id}) "row-header-widget"}}                                        '+
     '    </th>                                                                                             '+
     '    {{each(j, column) headers.columnHeaders}}                                                         '+
-    '        {{tmpl({data:data, row:row, column:column, table:label, row_id:row_id, column_id:j}) "cell"}} '+
+    '        {{tmpl({data:data, row:row, column:column, row_id:row_id, column_id:j}) "cell"}} '+
     '    {{/each}}                                                                                         '+
     '</tr>                                                                                                 '
 );

File jsonfield/static/js/json-table.js

     // Hang onto the source DOM object.
     var $dataSource = $(dataSource);
     var data = {};
-    var $tables;
+    var $table;
     var errors = [];
     
     var defaults = {
-        baseTableNames: [],
         baseRowHeaders: [],
         baseColumnHeaders: [],
         jsonIndent: '    ',
         defaultRowHeaderValue: "",
         defaultColumnHeaderValue: "",
         display: "inline",
-        stripEmptyData: true
+        stripEmptyData: true,
+        rule: {
+            days:[],
+            start:null, finish:null,
+            period:null, length:null
+        }
     };
     
     $.extend(defaults, options);
     function updateDataFromSource(){
         data = JSON.parse($dataSource.val());
         
-        // TODO: Better handle this - we need to build up some structure?
-        //       Change the sorted order of the JS object?
-        
-        $.each(defaults.baseTableNames, function(i, el){
-            if (!data[el]){
-                data[el] = {};
-            }
-        });
-        
-        if ($tables) {
-            $tables.detach();
+        if ($table) {
+            $table.detach();
         }
         
         if (defaults.display == "inline"){
-            $tables = $.tmpl('table', templateData()).appendTo($dataSource.parent());
+            $table = $.tmpl('table', templateData()).appendTo($dataSource.parent());
         } else {
-            $tables = $.tmpl('table', templateData()).appendTo('body');
+            $table = $.tmpl('table', templateData()).appendTo('body');
         }
     }
     
+    
     function getDataFromTable(){
         var newData = {};
-        $tables.find('input.cell-value').each(function(i, el){
+        $table.find('input.cell-value').each(function(i, el){
             $el = $(el);
-            // if ($el.val()){
-                var table = $el.attr('table');
-                var row = $el.attr('row');
-                var column = $el.attr('column');
-                pathValue(newData, [table, row], column, $el.val());
-            // }
+            var row = $el.attr('row');
+            var column = $el.attr('column');
+            if (!newData[row]){
+                newData[row] = {}
+            }
+            if ($el.val()) {
+                newData[row][column] = {rate: $el.val()};
+                if (getVisibleRules(column).length > 0) {
+                    newData[row][column]['rules'] = getVisibleRules(column);
+                }
+            }
         });
         return newData;
     }
     
     function stripEmptyData(){
-        $.each(data, function(label, table){
-            $.each(table, function(row, rowData){
-                $.each(rowData, function(column, value){
-                    if (!value){
-                        delete rowData[column];
-                    }
-                });
-                if ($.isEmptyObject(rowData)){
-                    delete table[row];
+        $.each(data, function(row, rowData){
+            $.each(rowData, function(column, value){
+                if (!value){
+                    delete rowData[column];
                 }
             });
-            if ($.isEmptyObject(table)){
-                delete data[label];
-            };
+            if ($.isEmptyObject(rowData)){
+                delete data[row];
+            }
         });
     }
     
     function updateSourceFromData(){
+        console.log("Updating source from data.");
         data = getDataFromTable();
         runValidations();
         
     }
     
     function updateErrors(){
-        $tables.find('tr, td, th').removeClass('errors');
-        $tables.parent().find('.errormsg').detach();
+        $table.find('tr, td, th').removeClass('errors');
+        $table.parent().find('.errormsg').detach();
         // DEBUG(errors);
         $.each(errors, function(i,el){
             $($(el).selector).addClass("errors");
             $($(el).selector).children().first().before('<div style="color:red;" class="errormsg">' + el.reason + "</div>")
         });
         if (errors.length){
-            $tables.first().before('<h3 style="color:red;" class="errormsg errors">Errors prevented saving</h3>');
+            $table.before('<h3 style="color:red;" class="errormsg errors">Errors prevented saving</h3>');
         }
     }
     
-    function pathValue(data, keys, last_key, value){
-        var curr = data;
-        $.each(keys, function(i, val){
-            if(!curr[val]){
-                curr[val] = {};
-            }
-            curr = curr[val];
-        });
-        curr[last_key] = value;
+    function templateData(){
+        var result = {
+            headers: getHeaderNames(),
+            conditions: getConditions(),
+            defaults: defaults,
+            data: data
+        };
+        
+        return result;
     }
     
-    function templateData(){
-        var tdata = [];
-        $.each(data, function(key, val){
-            tdata.push({
-                label: key,
-                headers: getHeaders(val),
-                data: val,
-                defaults: defaults
-            });
-        });
-        return tdata;
-    }
-    
-    function getHeaders(data){
+    function getHeaderNames(){
         var rowHeaders = [], columnHeaders = [];
         $.each(data, function(row, rowData){
             if (!inAny(row, [rowHeaders, defaults.baseRowHeaders])){
         };
     }
     
+    function getVisibleRules(conditionName) {
+        var rules = [];
+        $table
+            .find('th.column-header input[value="' + conditionName + '"]')
+                .closest('th')
+                    .find('.condition-rule')
+                        .each(function(i, form){
+                            rules.push(form2object(form));
+        });
+        return rules;
+    }
+    
     function getVisibleHeaders($table) {
         return {
             rowHeaders: $table.find('th.row-header input.heading-value').map(function(i,x){return x.value}),
-            columnHeaders: $table.find('th.column-header input.heading-value').map(function(i,x){return x.value})            
+            columnHeaders: $table.find('th.column-header input.condition-name').map(function(i,x){return x.value})            
         };
     }
     
+    function getRules(conditionName){
+        var rules = [];
+        $.each(data, function(row, rowData){
+            if (rowData[conditionName] && rowData[conditionName]['rules']) {
+                $.each(rowData[conditionName]['rules'], function(i, rule){
+                    rules.push($.extend({}, defaults.rule, rule));
+                });
+                return;
+            }
+        });
+        return rules;
+    }
+    
+    function getConditions(){
+        conditionNames = getHeaderNames()['columnHeaders'];
+        conditions = {};
+        $.each(conditionNames, function(i, conditionName){
+            conditions[conditionName] = getRules(conditionName);
+        });
+        console.log(conditions);
+        return conditions;
+    }
+    
     function inAny(value, arrays){
         // is the value in any of the passed in arrays?
-        result = false;
+        var result = false;
         $.each(arrays, function(i, val){
             if ($.inArray(value, val) > -1) {
                 result = true;
         }).get().join(sep);
     }
     
-    function headerChanged(kind, sep, innerSep){
-        return function(evt){
-            var $th = $(this).closest('th');
-            var $value = $th.find('input:hidden.heading-value');
-            var kind_id = $value.attr(kind + '_id');
-            var newValue = combineFields($th.find('.fragment').not(':hidden'), sep, innerSep);
-            $th.closest('table').find('input[' + kind + '_id="' + kind_id + '"]').attr(kind, newValue);
-            $value.val(newValue);
-            updateSourceFromData();
-            // DEBUG(newValue);
-        };
-    }
-    var rowHeaderChanged = headerChanged('row', ',', '');
-    var columnHeaderChanged = headerChanged('column', ';', ',');
     
     function addRow(evt){
-        var $table = $(this).closest('table');
-        var label = $table.attr('label');
+        evt.preventDefault();
         $.tmpl('row', {
             row:defaults.defaultRowHeaderValue,
             headers: getVisibleHeaders($table),
             data: data,
-            label: label,
             row_id: $table.find('tr.data-row').length
         }).insertBefore($(this).closest('tr'));
         // Disable adding a new row until this one has been edited.
         // $(this).attr('disabled','disabled');
+        return false;
         runValidations();
-        return false;
+
     }
     
     function addColumn(evt){
+        evt.preventDefault();
         // Add a new column header.
-        var $table = $(this).closest('table');
-        var label = $table.attr('label');
         var column = defaults.defaultColumnHeaderValue;
+        console.log(column);
         var column_id = $table.find('tr.header-row th.column-header').length;
+        console.log(column_id);
         $.tmpl('column-header', {
             column: column,
-            column_id: column_id
+            column_id: column_id,
+            rules: [defaults.rule]
         }).insertBefore($(this).closest('th'));
         // Now add a cell to each row in this table.
         $table.find('tr.data-row').each(function(i, el){
                 row_id:i,
                 row:$(el).find('th input.heading-value').val(),
                 column_id: column_id,
-                column: column,
-                table: label
+                column: column
             }).appendTo($(el));
         });
         runValidations();
-        return false;
+    }
+    
+    function updateColumnName(evt) {
+        evt.preventDefault();
+        var $target = $(evt.target);
+        var newName = $target.val();
+        var column_id = $target.attr('column_id');
+        $table.find('input.cell-value').filter(function(i){
+            return $(this).attr('column_id') == column_id;
+        }).attr('column', newName);
+    }
+    
+    function updateRowHeader(evt) {
+        evt.preventDefault();
+        var $th = $(evt.target).closest('th');
+        var newValue = combineFields($th.find(':input.fragment'), ',');
+        console.log(newValue);
+        $th.find(':input:hidden').val(newValue);
+        $th.closest('tr').find('input.cell-value').attr('row', newValue);
+    }
+    
+    function addRule(evt) {
+        // Add a new rule to the current condition
+        evt.preventDefault();
+        $.tmpl($('#rule-template'), defaults.rule).insertBefore($(evt.target))  
+    }
+    
+    function deleteRule(evt) {
+        // Remove the current rule from the current condition
+        evt.preventDefault();
+        $(evt.target).closest('.condition-rule').detach();
     }
     
     function preValidate(){
         errors = [];
     }
     
-    function findDuplicateHeaders(data){
+    function findDuplicateHeaders(){
         // Ensure that there are no duplicated row or column headings.
-        $tables.each(function(i, table){
-            $table = $(table);
-            $table.find('th.column-header').each(function(j, col){
-                $this = $(col).find('input:hidden.heading-value');
-                selector = $table.find('th.column-header input:hidden[value="' + $this.val() + '"]').not($this).closest('th');
-                if (selector.length){
-                    errors.push({
-                        selector:selector,
-                        reason: "Duplicate " + defaults.columnName
-                    });
-                }
-                
-            });
-            $table.find('th.row-header').each(function(j,row){
-                $this = $(row).find('input:hidden');
-                // DEBUG($this.val());
-                selector = $table.find('th.row-header input:hidden[value="'+ $this.val() + '"]').not($this).closest('th');
-                if (selector.length){
-                    errors.push({
-                        selector:selector,
-                        reason: "Duplicate " + defaults.rowName
-                    });
-                }
-            });
+        $table.find('.condition-name').each(function(j, col){
+            $this = $(col);
+            selector = $table.find('.condition-name[value="' + $this.val() + '"]').not($this).closest('th');
+            if (selector.length){
+                errors.push({
+                    selector:selector,
+                    reason: "Duplicate " + defaults.columnName
+                });
+            }
         });
         
-        
+        $table.find('th.row-header').each(function(j,row){
+            $this = $(row).find('input:hidden');
+            selector = $table.find('th.row-header input:hidden[value="'+ $this.val() + '"]').not($this).closest('th');
+            if (selector.length){
+                errors.push({
+                    selector:selector,
+                    reason: "Duplicate " + defaults.rowName
+                });
+            }
+        });
     }
     
     // Load the templates for performance reasons.
     Attach all of the event handlers
     */
     
-    $('th.column-header :input').live('change blur', columnHeaderChanged);
-    $('th.row-header :input').live('change blur', rowHeaderChanged);
-    $(':input.cell-value').live('change blur', updateSourceFromData);
+    $('.json-data-table :input').live('change blur', updateSourceFromData);
+    $('.json-data-table th .condition-name').live('change blur', updateColumnName);
+    $('.json-data-table th.row-header :input').live('change blur', updateRowHeader);
     $('.add-row').live('click', addRow);
     $('.add-column').live('click', addColumn);
+    $('.add-rule').live('click', addRule);
+    $('.delete-rule').live('click', deleteRule);
     $dataSource.live('change blur', updateDataFromSource);
     
-    $dataSource.hide();
+    // $dataSource.hide();
     
     return $;
 }

File jsonfield/widgets.py

         js = (
             settings.STATICFILES_URL + 'js/jquery.min.js',
             settings.STATICFILES_URL + 'js/jquery.tmpl.js',
+            settings.STATICFILES_URL + 'js/form2object.js',
             settings.STATICFILES_URL + 'js/json-table.js',
             settings.STATICFILES_URL + 'js/json-table-templates.js',
         )