Basic frontend uploader component

This commit is contained in:
dandds 2019-06-05 17:10:05 -04:00
parent e327a0bada
commit fb430e76e9
8 changed files with 114 additions and 20 deletions

View File

@ -1,6 +1,7 @@
from wtforms.fields import BooleanField, DecimalField, StringField from wtforms.fields import BooleanField, DecimalField, FileField, StringField
from wtforms.fields.html5 import DateField from wtforms.fields.html5 import DateField
from wtforms.validators import Required, Optional from wtforms.validators import Required, Optional
from flask_wtf.file import FileAllowed
from .forms import BaseForm from .forms import BaseForm
from atst.utils.localization import translate from atst.utils.localization import translate
@ -12,6 +13,13 @@ class TaskOrderForm(BaseForm):
description=translate("forms.task_order.number_description"), description=translate("forms.task_order.number_description"),
validators=[Required()], validators=[Required()],
) )
pdf = FileField(
None,
validators=[
FileAllowed(["pdf"], translate("forms.task_order.file_format_not_allowed"))
],
render_kw={"accept": ".pdf,application/pdf"},
)
class FundingForm(BaseForm): class FundingForm(BaseForm):

View File

@ -9,6 +9,7 @@ import levelofwarrant from '../levelofwarrant'
import multicheckboxinput from '../multi_checkbox_input' import multicheckboxinput from '../multi_checkbox_input'
import optionsinput from '../options_input' import optionsinput from '../options_input'
import textinput from '../text_input' import textinput from '../text_input'
import uploadinput from '../upload_input'
import toggler from '../toggler' import toggler from '../toggler'
export default { export default {
@ -23,6 +24,7 @@ export default {
optionsinput, optionsinput,
textinput, textinput,
toggler, toggler,
uploadinput,
}, },
mixins: [FormMixin], mixins: [FormMixin],
} }

View File

@ -29,7 +29,7 @@ export default {
const pdf = this.initialData const pdf = this.initialData
return { return {
showUpload: !pdf || this.uploadErrors.length > 0, attachment: pdf || null,
} }
}, },
@ -37,5 +37,24 @@ export default {
showUploadInput: function() { showUploadInput: function() {
this.showUpload = true this.showUpload = true
}, },
addAttachment: function(e) {
this.attachment = e.target.value
},
removeAttachment: function(e) {
e.preventDefault()
this.attachment = null
this.$refs.attachmentInput.value = null
},
},
computed: {
baseName: function() {
if (this.attachment) {
return this.attachment.split(/[\\/]/).pop()
}
},
hasAttachment: function() {
return !!this.attachment
},
}, },
} }

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg>

After

Width:  |  Height:  |  Size: 600 B

View File

@ -23,6 +23,7 @@
@import "elements/graphs"; @import "elements/graphs";
@import "elements/menu"; @import "elements/menu";
@import "elements/card"; @import "elements/card";
@import "elements/uploader";
@import "components/accordion_table"; @import "components/accordion_table";
@import "components/topbar"; @import "components/topbar";

View File

@ -0,0 +1,48 @@
.upload-widget {
label.upload-label {
text-align: right;
border: 1px solid black;
padding: 0;
display: block;
.upload-button {
padding: 1rem 1.5rem;
display: inline-block;
background-color: $color-blue;
border: 1px solid $color-blue;
margin: -1px;
color: white;
&:hover {
background-color: $color-blue-darker;
}
}
}
input {
opacity: 0;
}
}
.uploaded-file {
.icon {
vertical-align: middle;
svg * {
fill: $color-green;
}
}
.uploaded-file__name {
vertical-align: middle;
margin-left: 0.5rem;
font-weight: $font-bold;
text-decoration: underline;
}
.uploaded-file__remove {
vertical-align: middle;
margin-left: 2rem;
font-size: $small-font-size;
}
}

View File

@ -1,24 +1,37 @@
{% from "components/icon.html" import Icon %}
{% macro UploadInput(field, show_label=False) -%} {% macro UploadInput(field, show_label=False) -%}
<uploadinput inline-template v-bind:initial-data='{{ field.data | tojson }}' v-bind:upload-errors='{{ field.errors | list }}'> <uploadinput inline-template v-bind:initial-data='{{ field.data | tojson }}' v-bind:upload-errors='{{ field.errors | list }}'>
<div> <div>
<template v-if="showUpload"> <div v-show="hasAttachment" class="uploaded-file">
<div class="usa-input {% if field.errors %} usa-input--error {% endif %}"> {{ Icon("check-circle-solid") }}
<span class="uploaded-file__name" v-html="baseName"></span>
<a href="#" class="uploaded-file__remove" v-on:click="removeAttachment">Remove</a>
</div>
<div v-show="hasAttachment === false" class="usa-input {% if field.errors %} usa-input--error {% endif %}">
{% if show_label %} {% if show_label %}
{{ field.label }} {{ field.label }}
{% endif %} {% endif %}
{{ field.description }} {{ field.description }}
{{ field }} <div class="upload-widget">
<label class="upload-label" for="{{ field.name }}">
<span class="upload-button">
Browse
</span>
</label>
<input
v-on:change="addAttachment"
ref="attachmentInput"
accept="{{ field.accept }}"
id="{{ field.name }}"
name="{{ field.name }}"
aria-label="Task Order Upload"
type="file">
</div>
{% for error in field.errors %} {% for error in field.errors %}
<span class="usa-input__message">{{error}}</span> <span class="usa-input__message">{{error}}</span>
{% endfor %} {% endfor %}
</div> </div>
</template>
<template v-else>
<p>Uploaded {{ field.data.filename }}</p>
<div>
<button type="button" v-on:click="showUploadInput">Change</button>
</div>
</template>
</div> </div>
</uploadinput> </uploadinput>
{%- endmacro %} {%- endmacro %}

View File

@ -2,6 +2,7 @@
{% from 'components/save_button.html' import SaveButton %} {% from 'components/save_button.html' import SaveButton %}
{% from 'components/text_input.html' import TextInput %} {% from 'components/text_input.html' import TextInput %}
{% from 'components/upload_input.html' import UploadInput %}
{% block content %} {% block content %}
<div class="col task-order-form"> <div class="col task-order-form">
@ -11,7 +12,7 @@
{% include "portfolios/header.html" %} {% include "portfolios/header.html" %}
{% endblock %} {% endblock %}
<base-form inline-template> <base-form inline-template>
<form id="new-task-order" action='{{ url_for("task_orders.update", portfolio_id=portfolio.id) }}' method="POST" autocomplete="off"> <form id="new-task-order" action='{{ url_for("task_orders.update", portfolio_id=portfolio.id) }}' method="POST" autocomplete="off" enctype="multipart/form-data">
{{ form.csrf_token }} {{ form.csrf_token }}
<div class="panel__content"> <div class="panel__content">
<!-- TODO: implement save bar with component --> <!-- TODO: implement save bar with component -->
@ -28,6 +29,7 @@
{{ "task_orders.new.form_help_text" | translate }} {{ "task_orders.new.form_help_text" | translate }}
<hr> <hr>
{{ TextInput(form.number, validation='taskOrderNumber') }} {{ TextInput(form.number, validation='taskOrderNumber') }}
{{ UploadInput(form.pdf) }}
</div> </div>
</form> </form>
</base-form> </base-form>