Milimetric has submitted this change and it was merged.

Change subject: got knockout and wtforms communicating well
......................................................................


got knockout and wtforms communicating well

Change-Id: I65e110ca6e5404be751b13349beef1307da9336e
---
M wikimetrics/controllers/metrics.py
M wikimetrics/metrics/bytes_added.py
M wikimetrics/static/js/jobCreate.js
A wikimetrics/static/js/knockout.util.js
D wikimetrics/templates/form.html
A wikimetrics/templates/form_for_metrics.html
M wikimetrics/templates/request.html
7 files changed, 105 insertions(+), 57 deletions(-)

Approvals:
  Milimetric: Verified; Looks good to me, approved



diff --git a/wikimetrics/controllers/metrics.py 
b/wikimetrics/controllers/metrics.py
index fd6fc2e..8da1f17 100644
--- a/wikimetrics/controllers/metrics.py
+++ b/wikimetrics/controllers/metrics.py
@@ -60,9 +60,7 @@
         metric_form = metric_classes[name]()
     
     return render_template(
-        'form.html',
+        'form_for_metrics.html',
         form=metric_form,
-        form_class='metric-configuration',
         action=request.url,
-        submit_text='Save Configuration',
     )
diff --git a/wikimetrics/metrics/bytes_added.py 
b/wikimetrics/metrics/bytes_added.py
index 2bf7b47..f488b0a 100644
--- a/wikimetrics/metrics/bytes_added.py
+++ b/wikimetrics/metrics/bytes_added.py
@@ -1,9 +1,28 @@
 from metric import Metric
 from flask.ext import wtf
+from wtforms.compat import text_type
 
 __all__ = [
     'BytesAdded',
 ]
+
+
+def better_bool(value):
+    if type(value) is bool:
+        return value
+    elif type(value) is list:
+        value = value[0]
+    
+    return str(value).strip().lower() in ['yes', 'y', 'true']
+
+
+class BetterBooleanField(wtf.BooleanField):
+    
+    def process_formdata(self, valuelist):
+        # Checkboxes and submit buttons simply do not send a value when
+        # unchecked/not pressed. So the actual value="" doesn't matter for
+        # purpose of determining .data, only whether one exists or not.
+        self.data = better_bool(valuelist)
 
 
 class BytesAdded(Metric):
@@ -55,10 +74,10 @@
     start_date      = wtf.DateField()
     end_date        = wtf.DateField()
     namespace       = wtf.IntegerField(default=0)
-    positive_total  = wtf.BooleanField(default=True)
-    negative_total  = wtf.BooleanField(default=True)
-    absolute_total  = wtf.BooleanField(default=True)
-    net_total       = wtf.BooleanField(default=True)
+    positive_total  = BetterBooleanField(default=True)
+    negative_total  = BetterBooleanField(default=True)
+    absolute_total  = BetterBooleanField(default=True)
+    net_total       = BetterBooleanField(default=True)
     
     def __call__(self, user_ids, session):
         """
diff --git a/wikimetrics/static/js/jobCreate.js 
b/wikimetrics/static/js/jobCreate.js
index ea1917c..79e18d3 100644
--- a/wikimetrics/static/js/jobCreate.js
+++ b/wikimetrics/static/js/jobCreate.js
@@ -1,25 +1,6 @@
 $(document).ready(function(){
     // set up async handlers for any async forms
     // TODO: replace with a decent plugin
-    $(document).on('submit', 'form.metric-configuration', function(e){
-        e.preventDefault();
-        e.stopPropagation();
-        
-        var form = $(this);
-        
-        $.post(form.attr('action'), form.serialize())
-            .done(function(htmlToReplaceWith){
-                form.replaceWith(htmlToReplaceWith);
-                // if no validation errors, save the metric into the viewModel
-                if (form.find('ul.error-list').length === 0) {
-                    // save to viewModel.metrics
-                    // search in viewModel.metrics
-                    // set metric properties
-                }
-            })
-            .fail(function(){
-            });
-    });
     
     $(document).on('submit', 'form.job-request', function(e){
         // same thing as metric-configuration, but passing 
viewModel.request().responses()
@@ -41,24 +22,33 @@
 
         metrics: ko.observableArray([]),
         toggleMetric: function(metric){
-            // TODO: this should work but... doesn't?
-            // if (!metric.configure().length) {
-            // metric.configure = ko.observable();
-            // ...
-            // metric.configure(configureForm);
-            
             
             if (metric.selected()){
-            // fetch form to edit metric with
+                // fetch form to configure metric with
                 $.get('/metrics/configure/' + metric.name, 
function(configureForm){
-                    $(metric.tabIdSelector() + 
'-configure').html(configureForm);
+                    metric.configure(configureForm);
                 }).fail(failure);
             } else {
-                $(metric.tabIdSelector() + '-configure').html('');
+                metric.configure('');
             }
             return true;
         },
 
+        saveMetricConfiguration: function(formElement){
+            var metric = ko.dataFor(formElement);
+            var form = $(formElement);
+            var data = ko.toJS(metric);
+            delete data.configure;
+            
+            $.ajax({
+                type: 'post',
+                url: form.attr('action'),
+                data: data,
+            }).done(function(htmlToReplaceWith){
+                    metric.configure(htmlToReplaceWith);
+            }).fail(function(){
+            });
+        },
     };
     
     // fetch this user's cohorts
diff --git a/wikimetrics/static/js/knockout.util.js 
b/wikimetrics/static/js/knockout.util.js
new file mode 100644
index 0000000..d280b76
--- /dev/null
+++ b/wikimetrics/static/js/knockout.util.js
@@ -0,0 +1,36 @@
+/**
+ * Custom binding that is used as follows:
+ * `<section data-bind="subview: observableProperty"></section>`
+ * And works as follows:
+ *     In the example above, observableProperty is a ko.observable whose value 
is an object that has a `template` property
+ *     The binding finds the template with id `observableProperty().template` 
and fills it as the innerHTML of the section element
+ *     The binding then sets the context for the section's child elements as 
the observableProperty (like with: observableProperty)
+ */
+ko.bindingHandlers.metricConfigurationForm = {
+    init: function(element, valueAccessor, allBindingsAccessor, viewModel, 
bindingContext){
+        return {
+            controlsDescendantBindings: true
+        };
+    },
+    update: function(element, valueAccessor, allBindingsAccessor, viewModel, 
bindingContext){
+        var unwrapped, childContext;
+        unwrapped = ko.utils.unwrapObservable(valueAccessor());
+        if (unwrapped != null) {
+            $(unwrapped).find(':input').each(function(){
+                var value = '';
+                var name = $(this).attr('name');
+                if (!name) { return; }
+                
+                if ($(this).is('[type=checkbox]')){
+                    value = $(this).is(':checked');
+                } else {
+                    value = $(this).val();
+                }
+                bindingContext.$data[name] = ko.observable(value);
+            });
+            $(element).html(unwrapped);
+            childContext = 
bindingContext.createChildContext(bindingContext.$data);
+            ko.applyBindingsToDescendants(childContext, element);
+        }
+    }
+};
diff --git a/wikimetrics/templates/form.html b/wikimetrics/templates/form.html
deleted file mode 100644
index f79ce50..0000000
--- a/wikimetrics/templates/form.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<form class="form-horizontal {{form_class}}" method="POST" action="{{action}}">
-    {{ form.hidden_tag() }}
-    {% for f in form if f.label.text != 'Csrf Token' %}
-        <div class="control-group">
-            {{ f.label(class="control-label") }}
-            <div class="controls">
-                {{ f(placeholder=f.description) }}
-                {% if f.errors %}
-                    <ul class="unstyled error-list">
-                        {% for e in f.errors %}
-                        <li class="text-error">{{ e }}</li>
-                        {% endfor %}
-                    </ul>
-                {% endif %}
-            </div>
-        </div>
-    {% endfor %}
-    <div class="form-actions">
-        <input class="btn btn-primary" type="submit" value="{{submit_text}}"/>
-    </div>
-</form>
diff --git a/wikimetrics/templates/form_for_metrics.html 
b/wikimetrics/templates/form_for_metrics.html
new file mode 100644
index 0000000..055ca8e
--- /dev/null
+++ b/wikimetrics/templates/form_for_metrics.html
@@ -0,0 +1,25 @@
+<form class="form-horizontal metric-configuration" method="POST" 
action="{{action}}" data-bind="submit: $root.saveMetricConfiguration">
+    {{ form.hidden_tag() }}
+    {% for f in form if f.label.text != 'Csrf Token' %}
+        <div class="control-group">
+            {{ f.label(class="control-label") }}
+            <div class="controls">
+                {% if f.type == 'BooleanField' or f.type == 
'BetterBooleanField' %}
+                    {{ f(**{'data-bind':'checked: '+f.name}) }}
+                {% else %}
+                    {{ f(placeholder=f.description, **{'data-bind':'value: 
'+f.name}) }}
+                {% endif %}
+                {% if f.errors %}
+                    <ul class="unstyled error-list">
+                        {% for e in f.errors %}
+                        <li class="text-error">{{ e }}</li>
+                        {% endfor %}
+                    </ul>
+                {% endif %}
+            </div>
+        </div>
+    {% endfor %}
+    <div class="form-actions">
+        <input class="btn btn-primary" type="submit" value="Save 
Configuration"/>
+    </div>
+</form>
diff --git a/wikimetrics/templates/request.html 
b/wikimetrics/templates/request.html
index 6273b4f..73ed5b9 100644
--- a/wikimetrics/templates/request.html
+++ b/wikimetrics/templates/request.html
@@ -39,7 +39,7 @@
                     <span data-bind="text: description">
                     </span>
                 </label>
-                <div class="configure-metric-form" data-bind="attr: {id: 
tabId() + '-configure'}">
+                <div class="configure-metric-form" 
data-bind="metricConfigurationForm: configure, attr: {id: tabId() + 
'-configure'}">
                 </div>
             </div>
         </div>
@@ -88,5 +88,6 @@
 
 {% block scripts %}
 <script src="//ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js"></script>
+<script src="{{ url_for('static', filename='js/knockout.util.js') }}"></script>
 <script src="{{ url_for('static', filename='js/jobCreate.js') }}"></script>
 {% endblock %}

-- 
To view, visit https://gerrit.wikimedia.org/r/71542
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I65e110ca6e5404be751b13349beef1307da9336e
Gerrit-PatchSet: 1
Gerrit-Project: analytics/wikimetrics
Gerrit-Branch: master
Gerrit-Owner: Milimetric <dandree...@wikimedia.org>
Gerrit-Reviewer: Milimetric <dandree...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to