mirror of
https://github.com/frappe/books.git
synced 2024-12-24 11:55:46 +00:00
File (#80)
File doctype - Upload files using multer - uploadFiles API in http.js - Link File to doctype with name and field - Refactor File component for fullpaths - frappe.db.setValue(s) API
This commit is contained in:
parent
1aabf7ef40
commit
375325d917
@ -316,6 +316,12 @@ module.exports = class Database extends Observable {
|
|||||||
getFormattedValues(fields, doc) {
|
getFormattedValues(fields, doc) {
|
||||||
let values = fields.map(field => {
|
let values = fields.map(field => {
|
||||||
let value = doc[field.fieldname];
|
let value = doc[field.fieldname];
|
||||||
|
return this.getFormattedValue(field, value);
|
||||||
|
});
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedValue(field, value) {
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
if (field.fieldtype === 'Date') {
|
if (field.fieldtype === 'Date') {
|
||||||
// date
|
// date
|
||||||
@ -331,8 +337,6 @@ module.exports = class Database extends Observable {
|
|||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return values;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteMany(doctype, names) {
|
async deleteMany(doctype, names) {
|
||||||
@ -382,6 +386,16 @@ module.exports = class Database extends Observable {
|
|||||||
return row.length ? row[0][fieldname] : null;
|
return row.length ? row[0][fieldname] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setValue(doctype, name, fieldname, value) {
|
||||||
|
return await this.setValues(doctype, name, {
|
||||||
|
[fieldname]: value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async setValues(doctype, name, fieldValuePair) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', order = 'desc' } = {}) {
|
getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', order = 'desc' } = {}) {
|
||||||
// select {fields} from {doctype} where {filters} order by {orderBy} {order} limit {start} {limit}
|
// select {fields} from {doctype} where {filters} order by {orderBy} {order} limit {start} {limit}
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,21 @@ module.exports = class HTTPClient extends Observable {
|
|||||||
|
|
||||||
async insert(doctype, doc) {
|
async insert(doctype, doc) {
|
||||||
doc.doctype = doctype;
|
doc.doctype = doctype;
|
||||||
|
let filesToUpload = this.getFilesToUpload(doc);
|
||||||
let url = this.getURL('/api/resource', doctype);
|
let url = this.getURL('/api/resource', doctype);
|
||||||
return await this.fetch(url, {
|
|
||||||
|
const responseDoc = await this.fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(doc)
|
body: JSON.stringify(doc)
|
||||||
})
|
});
|
||||||
|
|
||||||
|
await this.uploadFilesAndUpdateDoc(filesToUpload, doctype, responseDoc);
|
||||||
|
|
||||||
|
return responseDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(doctype, name) {
|
async get(doctype, name) {
|
||||||
|
name = encodeURIComponent(name);
|
||||||
let url = this.getURL('/api/resource', doctype, name);
|
let url = this.getURL('/api/resource', doctype, name);
|
||||||
return await this.fetch(url, {
|
return await this.fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -36,15 +43,15 @@ module.exports = class HTTPClient extends Observable {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll({ doctype, fields, filters, start, limit, sort_by, order }) {
|
async getAll({ doctype, fields, filters, start, limit, sortBy, order }) {
|
||||||
let url = this.getURL('/api/resource', doctype);
|
let url = this.getURL('/api/resource', doctype);
|
||||||
|
|
||||||
url = url + "?" + frappe.getQueryString({
|
url = url + '?' + frappe.getQueryString({
|
||||||
fields: JSON.stringify(fields),
|
fields: JSON.stringify(fields),
|
||||||
filters: JSON.stringify(filters),
|
filters: JSON.stringify(filters),
|
||||||
start: start,
|
start: start,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
sort_by: sort_by,
|
sortBy: sortBy,
|
||||||
order: order
|
order: order
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -55,12 +62,17 @@ module.exports = class HTTPClient extends Observable {
|
|||||||
|
|
||||||
async update(doctype, doc) {
|
async update(doctype, doc) {
|
||||||
doc.doctype = doctype;
|
doc.doctype = doctype;
|
||||||
|
let filesToUpload = this.getFilesToUpload(doc);
|
||||||
let url = this.getURL('/api/resource', doctype, doc.name);
|
let url = this.getURL('/api/resource', doctype, doc.name);
|
||||||
|
|
||||||
return await this.fetch(url, {
|
const responseDoc = await this.fetch(url, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(doc)
|
body: JSON.stringify(doc)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.uploadFilesAndUpdateDoc(filesToUpload, doctype, responseDoc);
|
||||||
|
|
||||||
|
return responseDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(doctype, name) {
|
async delete(doctype, name) {
|
||||||
@ -104,6 +116,57 @@ module.exports = class HTTPClient extends Observable {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFilesToUpload(doc) {
|
||||||
|
const meta = frappe.getMeta(doc.doctype);
|
||||||
|
const fileFields = meta.getFieldsWith({ fieldtype: 'File' });
|
||||||
|
const filesToUpload = [];
|
||||||
|
|
||||||
|
if (fileFields.length > 0) {
|
||||||
|
fileFields.forEach(df => {
|
||||||
|
const files = doc[df.fieldname] || [];
|
||||||
|
if (files.length) {
|
||||||
|
filesToUpload.push({
|
||||||
|
fieldname: df.fieldname,
|
||||||
|
files: files
|
||||||
|
})
|
||||||
|
}
|
||||||
|
delete doc[df.fieldname];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return filesToUpload;
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFilesAndUpdateDoc(filesToUpload, doctype, doc) {
|
||||||
|
if (filesToUpload.length > 0) {
|
||||||
|
// upload files
|
||||||
|
for (const fileToUpload of filesToUpload) {
|
||||||
|
const files = await this.uploadFiles(fileToUpload.files, doctype, doc.name, fileToUpload.fieldname);
|
||||||
|
doc[fileToUpload.fieldname] = files[0].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFiles(fileList, doctype, name, fieldname) {
|
||||||
|
let url = this.getURL('/api/upload', doctype, name, fieldname);
|
||||||
|
|
||||||
|
let formData = new FormData();
|
||||||
|
for (const file of fileList) {
|
||||||
|
formData.append('files', file, file.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = await frappe.fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw Error(data.error);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
getURL(...parts) {
|
getURL(...parts) {
|
||||||
return this.protocol + '://' + this.server + (parts || []).join('/');
|
return this.protocol + '://' + this.server + (parts || []).join('/');
|
||||||
}
|
}
|
||||||
|
@ -154,6 +154,30 @@ module.exports = class sqliteDatabase extends Database {
|
|||||||
await frappe.db.run('delete from SingleValue where parent=?', name)
|
await frappe.db.run('delete from SingleValue where parent=?', name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setValues(doctype, name, fieldValuePair) {
|
||||||
|
const meta = frappe.getMeta(doctype);
|
||||||
|
const validFields = this.getKeys(doctype);
|
||||||
|
const validFieldnames = validFields.map(df => df.fieldname);
|
||||||
|
const fieldsToUpdate = Object.keys(fieldValuePair)
|
||||||
|
.filter(fieldname => validFieldnames.includes(fieldname))
|
||||||
|
|
||||||
|
// assignment part of query
|
||||||
|
const assigns = fieldsToUpdate.map(fieldname => `${fieldname} = ?`);
|
||||||
|
|
||||||
|
// values
|
||||||
|
const values = fieldsToUpdate.map(fieldname => {
|
||||||
|
const field = meta.getField(fieldname);
|
||||||
|
const value = fieldValuePair[fieldname];
|
||||||
|
return this.getFormattedValue(field, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// additional name for where clause
|
||||||
|
values.push(name);
|
||||||
|
|
||||||
|
return await this.run(`update ${doctype}
|
||||||
|
set ${assigns.join(', ')} where name=?`, values);
|
||||||
|
}
|
||||||
|
|
||||||
getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', groupBy, order = 'desc' } = {}) {
|
getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', groupBy, order = 'desc' } = {}) {
|
||||||
if (!fields) {
|
if (!fields) {
|
||||||
fields = frappe.getMeta(doctype).getKeywordFields();
|
fields = frappe.getMeta(doctype).getKeywordFields();
|
||||||
|
@ -29,6 +29,25 @@ module.exports = class BaseMeta extends BaseDocument {
|
|||||||
return this._field_map[fieldname];
|
return this._field_map[fieldname];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fields filtered by filters
|
||||||
|
* @param {Object} filters
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* meta = frappe.getMeta('ToDo')
|
||||||
|
* dataFields = meta.getFieldsWith({ fieldtype: 'Data' })
|
||||||
|
*/
|
||||||
|
getFieldsWith(filters) {
|
||||||
|
return this.fields.filter(df => {
|
||||||
|
let match = true;
|
||||||
|
for (const key in filters) {
|
||||||
|
const value = filters[key];
|
||||||
|
match = df[key] === value;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getLabel(fieldname) {
|
getLabel(fieldname) {
|
||||||
return this.getField(fieldname).label;
|
return this.getField(fieldname).label;
|
||||||
}
|
}
|
||||||
|
67
models/doctype/File/File.js
Normal file
67
models/doctype/File/File.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
module.exports = {
|
||||||
|
name: 'File',
|
||||||
|
doctype: 'DocType',
|
||||||
|
isSingle: 0,
|
||||||
|
keywordFields: [
|
||||||
|
'name',
|
||||||
|
'filename'
|
||||||
|
],
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldname: 'name',
|
||||||
|
label: 'File Path',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
required: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'filename',
|
||||||
|
label: 'File Name',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
required: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'mimetype',
|
||||||
|
label: 'MIME Type',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'size',
|
||||||
|
label: 'File Size',
|
||||||
|
fieldtype: 'Int',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'referenceDoctype',
|
||||||
|
label: 'Reference DocType',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'referenceName',
|
||||||
|
label: 'Reference Name',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'referenceField',
|
||||||
|
label: 'Reference Field',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
layout: [
|
||||||
|
{
|
||||||
|
columns: [
|
||||||
|
{ fields: ['filename'] },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columns: [
|
||||||
|
{ fields: ['mimetype'] },
|
||||||
|
{ fields: ['size'] },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columns: [
|
||||||
|
{ fields: ['referenceDoctype'] },
|
||||||
|
{ fields: ['referenceName'] },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
@ -1,47 +1,47 @@
|
|||||||
const indicatorColor = require('frappejs/ui/constants/indicators');
|
const { BLUE, GREEN } = require('frappejs/ui/constants/indicators');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: "ToDo",
|
name: 'ToDo',
|
||||||
label: "To Do",
|
label: 'To Do',
|
||||||
naming: "autoincrement",
|
naming: 'autoincrement',
|
||||||
pageSettings: {
|
pageSettings: {
|
||||||
hideTitle: true
|
hideTitle: true
|
||||||
},
|
},
|
||||||
"isSingle": 0,
|
isSingle: 0,
|
||||||
"keywordFields": [
|
keywordFields: [
|
||||||
"subject",
|
'subject',
|
||||||
"description"
|
'description'
|
||||||
],
|
],
|
||||||
titleField: 'subject',
|
titleField: 'subject',
|
||||||
indicators: {
|
indicators: {
|
||||||
key: 'status',
|
key: 'status',
|
||||||
colors: {
|
colors: {
|
||||||
Open: indicatorColor.BLUE,
|
Open: BLUE,
|
||||||
Closed: indicatorColor.GREEN
|
Closed: GREEN
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fields": [
|
fields: [
|
||||||
{
|
{
|
||||||
"fieldname": "subject",
|
fieldname: 'subject',
|
||||||
"label": "Subject",
|
label: 'Subject',
|
||||||
"fieldtype": "Data",
|
fieldtype: 'Data',
|
||||||
"required": 1
|
required: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "status",
|
fieldname: 'status',
|
||||||
"label": "Status",
|
label: 'Status',
|
||||||
"fieldtype": "Select",
|
fieldtype: 'Select',
|
||||||
"options": [
|
options: [
|
||||||
"Open",
|
'Open',
|
||||||
"Closed"
|
'Closed'
|
||||||
],
|
],
|
||||||
"default": "Open",
|
default: 'Open',
|
||||||
"required": 1
|
required: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "description",
|
fieldname: 'description',
|
||||||
"label": "Description",
|
label: 'Description',
|
||||||
"fieldtype": "Text"
|
fieldtype: 'Text'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ module.exports = {
|
|||||||
SystemSettings: require('./doctype/SystemSettings/SystemSettings.js'),
|
SystemSettings: require('./doctype/SystemSettings/SystemSettings.js'),
|
||||||
ToDo: require('./doctype/ToDo/ToDo.js'),
|
ToDo: require('./doctype/ToDo/ToDo.js'),
|
||||||
User: require('./doctype/User/User.js'),
|
User: require('./doctype/User/User.js'),
|
||||||
UserRole: require('./doctype/UserRole/UserRole.js')
|
UserRole: require('./doctype/UserRole/UserRole.js'),
|
||||||
|
File: require('./doctype/File/File.js'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
"luxon": "^1.0.0",
|
"luxon": "^1.0.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"morgan": "^1.9.0",
|
"morgan": "^1.9.0",
|
||||||
|
"multer": "^1.3.1",
|
||||||
"mysql": "^2.15.0",
|
"mysql": "^2.15.0",
|
||||||
"node-fetch": "^1.7.3",
|
"node-fetch": "^1.7.3",
|
||||||
"node-sass": "^4.7.2",
|
"node-sass": "^4.7.2",
|
||||||
|
@ -18,12 +18,15 @@ const auth = require('./../auth/auth')();
|
|||||||
const morgan = require('morgan');
|
const morgan = require('morgan');
|
||||||
const { addWebpackMiddleware } = require('../webpack/serve');
|
const { addWebpackMiddleware } = require('../webpack/serve');
|
||||||
const { getAppConfig } = require('../webpack/utils');
|
const { getAppConfig } = require('../webpack/utils');
|
||||||
const appConfig = getAppConfig();
|
|
||||||
|
frappe.conf = getAppConfig();
|
||||||
|
|
||||||
require.extensions['.html'] = function (module, filename) {
|
require.extensions['.html'] = function (module, filename) {
|
||||||
module.exports = fs.readFileSync(filename, 'utf8');
|
module.exports = fs.readFileSync(filename, 'utf8');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async start({backend, connectionParams, models, authConfig=null}) {
|
async start({backend, connectionParams, models, authConfig=null}) {
|
||||||
await this.init();
|
await this.init();
|
||||||
@ -39,9 +42,8 @@ module.exports = {
|
|||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
|
|
||||||
for (let staticPath of [appConfig.distPath, appConfig.staticPath]) {
|
app.use(express.static(frappe.conf.distPath));
|
||||||
app.use(express.static(staticPath));
|
app.use('/static', express.static(frappe.conf.staticPath))
|
||||||
}
|
|
||||||
|
|
||||||
app.use(morgan('tiny'));
|
app.use(morgan('tiny'));
|
||||||
|
|
||||||
@ -65,7 +67,7 @@ module.exports = {
|
|||||||
addWebpackMiddleware(app);
|
addWebpackMiddleware(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
frappe.config.port = appConfig.dev.devServerPort
|
frappe.config.port = frappe.conf.dev.devServerPort;
|
||||||
|
|
||||||
// listen
|
// listen
|
||||||
server.listen(frappe.config.port, () => {
|
server.listen(frappe.config.port, () => {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
const frappe = require('frappejs');
|
const frappe = require('frappejs');
|
||||||
|
const path = require('path');
|
||||||
|
const multer = require('multer');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
setup(app) {
|
setup(app) {
|
||||||
@ -43,6 +45,45 @@ module.exports = {
|
|||||||
return response.json(doc.getValidDict());
|
return response.json(doc.getValidDict());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const upload = multer({
|
||||||
|
storage: multer.diskStorage({
|
||||||
|
destination: (req, file, cb) => {
|
||||||
|
cb(null, frappe.conf.staticPath)
|
||||||
|
},
|
||||||
|
filename: (req, file, cb) => {
|
||||||
|
const filename = file.originalname.split('.')[0];
|
||||||
|
const extension = path.extname(file.originalname);
|
||||||
|
const now = Date.now();
|
||||||
|
cb(null, filename + '-' + now + extension);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/upload/:doctype/:name/:fieldname', upload.array('files', 10), frappe.asyncHandler(async function(request, response) {
|
||||||
|
const files = request.files;
|
||||||
|
const { doctype, name, fieldname } = request.params;
|
||||||
|
|
||||||
|
let fileDocs = [];
|
||||||
|
for (let file of files) {
|
||||||
|
const doc = frappe.newDoc({
|
||||||
|
doctype: 'File',
|
||||||
|
name: path.join('/', file.path),
|
||||||
|
filename: file.originalname,
|
||||||
|
mimetype: file.mimetype,
|
||||||
|
size: file.size,
|
||||||
|
referenceDoctype: doctype,
|
||||||
|
referenceName: name,
|
||||||
|
referenceFieldname: fieldname
|
||||||
|
});
|
||||||
|
await doc.insert();
|
||||||
|
|
||||||
|
await frappe.db.setValue(doctype, name, fieldname, doc.name);
|
||||||
|
|
||||||
|
fileDocs.push(doc.getValidDict());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json(fileDocs);
|
||||||
|
}));
|
||||||
|
|
||||||
// get document
|
// get document
|
||||||
app.get('/api/resource/:doctype/:name', frappe.asyncHandler(async function(request, response) {
|
app.get('/api/resource/:doctype/:name', frappe.asyncHandler(async function(request, response) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<form :class="['frappe-form-layout', { 'was-validated': invalid }]">
|
<form :class="['frappe-form-layout', { 'was-validated': invalid }]">
|
||||||
<div class="row" v-if="layoutConfig"
|
<div class="form-row" v-if="layoutConfig"
|
||||||
v-for="(section, i) in layoutConfig.sections" :key="i"
|
v-for="(section, i) in layoutConfig.sections" :key="i"
|
||||||
v-show="showSection(i)"
|
v-show="showSection(i)"
|
||||||
>
|
>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<list-actions
|
<list-actions
|
||||||
:doctype="doctype"
|
:doctype="doctype"
|
||||||
:showDelete="checkList.length"
|
:showDelete="checkList.length"
|
||||||
@new="newDoc"
|
@new="$emit('newDoc')"
|
||||||
@delete="deleteCheckedItems"
|
@delete="deleteCheckedItems"
|
||||||
/>
|
/>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
@ -60,18 +60,18 @@ export default {
|
|||||||
this.updateList();
|
this.updateList();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async newDoc() {
|
|
||||||
let doc = await frappe.getNewDoc(this.doctype);
|
|
||||||
this.$router.push(`/edit/${this.doctype}/${doc.name}`);
|
|
||||||
},
|
|
||||||
async updateList(query = null) {
|
async updateList(query = null) {
|
||||||
let filters = null
|
let filters = null;
|
||||||
if (query) {
|
if (query) {
|
||||||
filters = {
|
filters = {
|
||||||
keywords: ['like', query]
|
keywords: ['like', query]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const indicatorField = this.hasIndicator ? this.meta.indicators.key : null;
|
const indicatorField = this.hasIndicator
|
||||||
|
? this.meta.indicators.key
|
||||||
|
: null;
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
'name',
|
'name',
|
||||||
indicatorField,
|
indicatorField,
|
||||||
@ -89,7 +89,7 @@ export default {
|
|||||||
},
|
},
|
||||||
openForm(name) {
|
openForm(name) {
|
||||||
this.activeItem = name;
|
this.activeItem = name;
|
||||||
this.$router.push(`/edit/${this.doctype}/${name}`);
|
this.$emit('openForm', name);
|
||||||
},
|
},
|
||||||
async deleteCheckedItems() {
|
async deleteCheckedItems() {
|
||||||
await frappe.db.deleteMany(this.doctype, this.checkList);
|
await frappe.db.deleteMany(this.doctype, this.checkList);
|
||||||
@ -112,7 +112,7 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../styles/variables";
|
@import '../../styles/variables';
|
||||||
|
|
||||||
.list-group-item {
|
.list-group-item {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
|
@ -3,38 +3,55 @@ import Base from './Base';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
extends: Base,
|
extends: Base,
|
||||||
computed: {
|
|
||||||
inputClass() {
|
|
||||||
return ['d-none'];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
getWrapperElement(h) {
|
getWrapperElement(h) {
|
||||||
let fileName = this.docfield.placeholder || this._('Choose a file..');
|
let fileName = this.docfield.placeholder || this._('Choose a file..');
|
||||||
|
let filePath = null;
|
||||||
|
|
||||||
if (this.$refs.input && this.$refs.input.files.length) {
|
if (this.value && typeof this.value === 'string') {
|
||||||
|
filePath = this.value;
|
||||||
|
}
|
||||||
|
else if (this.$refs.input && this.$refs.input.files.length) {
|
||||||
fileName = this.$refs.input.files[0].name;
|
fileName = this.$refs.input.files[0].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileButton = h('button', {
|
const fileLink = h('a', {
|
||||||
class: ['btn btn-outline-secondary btn-block'],
|
|
||||||
domProps: {
|
|
||||||
textContent: fileName
|
|
||||||
},
|
|
||||||
attrs: {
|
attrs: {
|
||||||
type: 'button'
|
href: filePath,
|
||||||
|
target: '_blank'
|
||||||
},
|
},
|
||||||
on: {
|
domProps: {
|
||||||
click: () => this.$refs.input.click()
|
textContent: this._('View File')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return h('div', {
|
const helpText = h('small', {
|
||||||
|
class: 'form-text text-muted'
|
||||||
|
}, [fileLink]);
|
||||||
|
|
||||||
|
const fileNameLabel = h('label', {
|
||||||
|
class: ['custom-file-label'],
|
||||||
|
domProps: {
|
||||||
|
textContent: filePath || fileName
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileInputWrapper = h('div', {
|
||||||
|
class: ['custom-file']
|
||||||
|
},
|
||||||
|
[this.getInputElement(h), fileNameLabel, filePath ? helpText : null]
|
||||||
|
);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
class: ['form-group', ...this.wrapperClass],
|
class: ['form-group', ...this.wrapperClass],
|
||||||
attrs: {
|
attrs: {
|
||||||
'data-fieldname': this.docfield.fieldname
|
'data-fieldname': this.docfield.fieldname
|
||||||
}
|
}
|
||||||
}, [this.getLabelElement(h), this.getInputElement(h), fileButton]);
|
},
|
||||||
|
[this.getLabelElement(h), fileInputWrapper]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
getInputAttrs() {
|
getInputAttrs() {
|
||||||
return {
|
return {
|
||||||
@ -48,6 +65,9 @@ export default {
|
|||||||
accept: (this.docfield.filetypes || []).join(',')
|
accept: (this.docfield.filetypes || []).join(',')
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getInputClass() {
|
||||||
|
return 'custom-file-input';
|
||||||
|
},
|
||||||
getInputListeners() {
|
getInputListeners() {
|
||||||
return {
|
return {
|
||||||
change: e => {
|
change: e => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="frappe-list-form row no-gutters">
|
<div class="frappe-list-form row no-gutters">
|
||||||
<div class="col-4 border-right">
|
<div class="col-4 border-right">
|
||||||
<frappe-list :doctype="doctype" :key="doctype" />
|
<frappe-list :doctype="doctype" :key="doctype" @newDoc="openNewDoc" @openForm="openForm" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<frappe-form v-if="name" :key="doctype + name" :doctype="doctype" :name="name" @save="onSave" />
|
<frappe-form v-if="name" :key="doctype + name" :doctype="doctype" :name="name" @save="onSave" />
|
||||||
@ -23,9 +23,17 @@ export default {
|
|||||||
if (doc.name !== this.$route.params.name) {
|
if (doc.name !== this.$route.params.name) {
|
||||||
this.$router.push(`/edit/${doc.doctype}/${doc.name}`);
|
this.$router.push(`/edit/${doc.doctype}/${doc.name}`);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
openForm(name) {
|
||||||
|
name = encodeURIComponent(name);
|
||||||
|
this.$router.push(`/edit/${this.doctype}/${name}`);
|
||||||
|
},
|
||||||
|
async openNewDoc() {
|
||||||
|
let doc = await frappe.getNewDoc(this.doctype);
|
||||||
|
this.$router.push(`/edit/${this.doctype}/${doc.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.frappe-list-form {
|
.frappe-list-form {
|
||||||
|
Loading…
Reference in New Issue
Block a user