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