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 <[email protected]>
Gerrit-Reviewer: Milimetric <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits