diff --git a/attract/__init__.py b/attract/__init__.py index e3bcbc1..e8d3e83 100644 --- a/attract/__init__.py +++ b/attract/__init__.py @@ -57,6 +57,11 @@ class AttractExtension(PillarExtension): import os.path return os.path.join(os.path.dirname(__file__), 'templates') + @property + def static_path(self): + import os.path + return os.path.join(os.path.dirname(__file__), 'static') + def setup_app(self, app): """Connects Blinker signals.""" diff --git a/attract/static/js/tasks.js b/attract/static/js/tasks.js new file mode 100644 index 0000000..43f0194 --- /dev/null +++ b/attract/static/js/tasks.js @@ -0,0 +1,29 @@ +function save_task(task_id, task_url) { + console.log('Saving task to', task_url); + + var $form = $('.task form'); + var $button = $form.find("button[type='submit']"); + var payload = $form.serialize(); + + $button.attr('disabled', true); + + if (console) console.log('Sending:', payload); + $.post(task_url, payload) + .done(function(data) { + if (console) console.log('Done saving', data); + + // Update the task list. + // NOTE: this is tightly linked to the HTML of the task list in for_project.jade. + $('#task-' + task_id).text($form.find("input[name='name']").val()); + }) + .fail(function(xhr_or_response_data) { + // jQuery sends the response data (if JSON), or an XHR object (if not JSON). + if (console) console.log('Failed saving', xhr_or_response_data); + }) + .always(function() { + $button.attr('disabled', false); + }) + ; + + return false; // prevent synchronous POST to current page. +} diff --git a/attract/tasks.py b/attract/tasks.py index 1b47f9a..95c0582 100644 --- a/attract/tasks.py +++ b/attract/tasks.py @@ -1,6 +1,6 @@ import logging -from flask import Blueprint, render_template +from flask import Blueprint, render_template, request import flask import flask_login @@ -39,10 +39,28 @@ def for_project(project): def view_embed_task(project, task_id): api = pillar_api() task = pillarsdk.Node.find(task_id, api=api) + node_type = project.get_node_type('attract.task') return render_template('attract/tasks/view_task_embed.html', task=task, - project=project) + project=project, + task_node_type=node_type) + + +@blueprint.route('//', methods=['POST']) +@attract_project_view() +def save(project, task_id): + log.info('Saving task %s', task_id) + log.debug('Form data: %s', request.form) + api = pillar_api() + task = pillarsdk.Node.find(task_id, api=api) + + task.name = request.form['name'] + task.description = request.form['description'] + task.properties.status = request.form['status'] + task.update(api=api) + + return flask.jsonify({'task_id': task_id, 'etag': task._etag}) @blueprint.route('//create') diff --git a/src/templates/attract/tasks/for_project.jade b/src/templates/attract/tasks/for_project.jade index 6dab73a..14eefd0 100644 --- a/src/templates/attract/tasks/for_project.jade +++ b/src/templates/attract/tasks/for_project.jade @@ -14,6 +14,7 @@ .col-md-4 .list-group#task-list | {% for task in tasks %} + //- NOTE: this is tightly linked to the JS in tasks.js. a.list-group-item(id="task-{{task._id}}",href="javascript:open_task('{{ task._id }}');") {{ task.name }} | {% endfor %} .col-md-4 diff --git a/src/templates/attract/tasks/view_task_embed.jade b/src/templates/attract/tasks/view_task_embed.jade index 7e40824..c956b37 100644 --- a/src/templates/attract/tasks/view_task_embed.jade +++ b/src/templates/attract/tasks/view_task_embed.jade @@ -1,8 +1,17 @@ +script(src="{{ url_for('static_attract', filename='js/tasks.js') }}",async=true) .task - dl - dt Name - dd {{ task.name }} - dt Description - dd {{ task.description }} - dt Status - dd {{ task.properties.status }} + form(onsubmit="return save_task('{{task._id}}', '{{ url_for('attract.tasks.save', project_url=project['url'], task_id=task._id) }}')") + .input-group + span.input-group-addon#task-addon-name(title='Name') N + input.form-control(name="name",autofocus=true,type=text,placeholder='Task name',aria-describedby="task-addon-name",value="{{ task.name|hide_none }}") + .input-group + span.input-group-addon#task-addon-description(title='Description') D + textarea.form-control(name="description",type=text,placeholder='Task description',aria-describedby="task-addon-description") {{ task.description|hide_none }} + .input-group + span.input-group-addon#task-addon-status(title='Status') S + select.form-control(name="status",aria-describedby="task-addon-status") + | {% for status in task_node_type.dyn_schema.status.allowed %} + | + | {% endfor %} + .input-group + button.btn.btn-default(type=submit) Save task