Related
I want to upload a file with Parasails. But I have a error :
<- POST /api/v1/admin/create-article (3ms 400)
| no file attached
| No file was attached.
°
I suppose to my syntaxe is not good.
My html code :
<div class="form-group">
<label for="imgFile">Image:</label>
<input class="form-control-file" id="imgFile" type="file" :class="[formErrors.imgFile ? 'is-invalid' : '']" autocomplete="imgFile">
<div class="invalid-feedback" v-if="formErrors.imgFile">S'il vous plaît, entrez une image valide.</div>
</div>
My action 2 :
module.exports = {
files: ['imgFile'],
friendlyName: 'Create article',
description: '',
inputs: {
imgFile: {
description: 'Upstream for an incoming file upload.',
type: 'ref'
},
titre: {
type: 'string',
required: true,
},
description: {
type: 'string',
required: true,
},
contenue: {
type: 'string',
required: true,
},
postDate:{
type: 'string',
required: false,
},
etiquette:{
type: 'number',
required: false,
},
sharingLink:{
type: 'string',
required: false,
}
},
exits: {
success: {
outputDescription: 'The newly created `Thing`.',
outputExample: {}
},
noFileAttached: {
description: 'No file was attached.',
responseType: 'badRequest'
},
tooBig: {
description: 'The file is too big.',
responseType: 'badRequest'
},
},
fn: async function (inputs, exits) {
var util = require('util');
// Upload the image.
var info = await sails.uploadOne(inputs.imgFile, {
maxBytes: 3000000
})
// Note: E_EXCEEDS_UPLOAD_LIMIT is the error code for exceeding
// `maxBytes` for both skipper-disk and skipper-s3.
.intercept('E_EXCEEDS_UPLOAD_LIMIT', 'tooBig')
.intercept((err)=>new Error('The photo upload failed: '+util.inspect(err)));
if(!info) {
throw 'noFileAttached';
}
var unurl = await sails.helpers.convertUrl(inputs.titre);
await Article.create({titre:inputs.titre, description:inputs.description, contenue:inputs.contenue ,postDate:inputs.postDate ,sharingLink:inputs.sharingLink,url:unurl, etiquette:inputs.etiquette}).fetch();
return exits.success();
}
};
My Root :
'POST /api/v1/admin/create-article': { action: 'admin/create-article' },
I have add this package on my project : sails-hook-uploads
I'm new to Sails and I do not understand this error.
Thanks!
How to upload a file using the new controller actions format in sails.js
Thanks you for your answers !
I think it's a front-end problem. When I make a "console.log" on "inputs" in my action:
{ imgFile:
Upstream {
_fatalErrors: [],
isNoop: true,
_files: [],
timeouts: { untilFirstFileTimer: [Object], untilMaxBufferTimer: [Object] },
_readableState:
ReadableState {
objectMode: true,
highWaterMark: 16,
buffer: [Object],
length: 0,
pipes: null,
pipesCount: 0,
flowing: null,
ended: false,
endEmitted: false,
reading: false,
sync: true,
needReadable: false,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null },
readable: true,
domain: null,
_events: { error: [Function] },
_eventsCount: 1,
_maxListeners: undefined,
fieldName: 'NOOP_imgFile' },
titre: 'inputs inputs inputs inputs',
description: 'inputs inputs inputs',
contenue: 'inputs inputs inputs',
postDate: '2019-12-21T09:13',
etiquette: 1 }
On the Chrome dev tool :
response of the tool
I think is not a websocket request is XMLHttpRequest (type;xhr).
module.exports = {
friendlyName: 'Update profile avatar',
description: '',
inputs: {
},
exits: {
success: {
responseType: 'redirect'
},
failure: {
responseType: 'redirect'
},
unhandleError: {
responseType: 'redirect',
}
},
fn: async function (inputs, exits) {
let req = this.req;
let res = this.res;
req.file('avatar').upload({
saveAs: fileName,
dirname: require('path').resolve(sails.config.appPath,
'assets/uploads')
}, function whenDone(err, uploadedFile) {
// your code here
});
}
};
This is my code in controller when using req.file('file-name').upload() function. No need to define file in inputs because using req
I have jqgrid, which sends row's data to another view (MVC4) when row is selected. But when I edit row's info (I'm using inline edit) this view doesn't changed. And I can't find event that is taking place after inline edit. This is js, what should I change to change my view after row editing
$(function () {
$("#GridTable").jqGrid({
url: "/Goods/GoodsList",
editurl: "/Goods/Edit",
datatype: 'json',
mtype: 'Get',
colNames: ['GoodId', 'Имя', 'Цена'],
colModel: [
{ key: true, hidden: true, name: 'GoodId', index: 'GoodId', editable: true },
{
key: false, name: 'GoodName', index: 'GoodName', editable: true, sortable: true,
editrules: {
required: true, custom: true, custom_func: notATag
}
},
{
key: false, name: 'Price', index: 'Price', editable: true, sortable: true, formatter: numFormat,
unformat: numUnformat,
//sorttype: 'float',
editrules: { required: true, custom: true, custom_func: figureValid}
}, ],
pager: jQuery('#pager'),
rowNum: 10,
rowList: [10, 25, 50, 100],
height: '100%',
viewrecords: true,
caption: 'Список товаров',
sortable: true,
emptyrecords: 'No records to display',
cellsubmit : 'remote',
jsonReader: {
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: false,
Id: "0"
},
//to get good's full view when row is selected
onSelectRow:
function () {
var myGrid = $('#GridTable'),
selRowId = myGrid.jqGrid('getGridParam', 'selrow'),
celValue = myGrid.jqGrid('getCell', selRowId, 'GoodId');
$.ajax({
url: "/Goods/DetailInfo",
type: "GET",
data: { id: celValue }
})
.done(function (partialViewResult) {
$("#goodDetInfo").html(partialViewResult);
});
},
//to change good's full view after row deleting
loadComplete: function(data){
var myGrid = $('#GridTable'),
selRowId = myGrid.jqGrid('getGridParam', 'selrow'),
celValue = myGrid.jqGrid('getCell', selRowId, 'GoodId');
$.ajax({
url: "/Goods/DetailInfo",
type: "GET",
data: { id: celValue }
})
.done(function (partialViewResult) {
$("#goodDetInfo").html(partialViewResult);
});
},
autowidth: true,
multiselect: false
}).navGrid('#pager', { edit: false, add: true, del: true, search: false, refresh: true },
{
// edit options
zIndex: 100,
url: '/Goods/Edit',
closeOnEscape: true,
closeAfterEdit: true,
recreateForm: true,
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
var myGrid = $('#GridTable'),
selRowId = myGrid.jqGrid('getGridParam', 'selrow'),
celValue = myGrid.jqGrid('getCell', selRowId, 'GoodId');
$.ajax({
url: "/Goods/DetailInfo",
type: "GET",
data: { id: celValue }
})
.done(function (partialViewResult) {
$("#goodDetInfo").html(partialViewResult);
});
}
},
{
// add options
zIndex: 100,
url: "/Goods/Create",
closeOnEscape: true,
closeAfterAdd: true,
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
}
},
{
// delete options
zIndex: 100,
url: "/Goods/Delete",
closeOnEscape: true,
closeAfterDelete: true,
recreateForm: true,
msg: "Are you sure you want to delete this task?",
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
}
});
$('#GridTable').inlineNav('#pager', {
edit: true,
add: false,
del: false,
cancel: true,
editParams: {
keys: true,
afterSubmit: function (response) {
if (response.responseText) {
alert(response.responseText);
}
var myGrid = $('#GridTable'),
selRowId = myGrid.jqGrid('getGridParam', 'selrow'),
celValue = myGrid.jqGrid('getCell', selRowId, 'GoodId');
$.ajax({
url: "/Goods/DetailInfo",
type: "GET",
data: { id: celValue }
})
.done(function (partialViewResult) {
$("#goodDetInfo").html(partialViewResult);
});
}
},
});
});
The properties and callback function of editParams parameter of inlineNav can be found here. What you need is probably aftersavefunc or successfunc instead of afterSubmit, which exist only in form editing method (see here). The parameters of aftersavefunc or successfunc callbacks are described here (as parameters of saveRow), but the parameters depend on the version of jqGrid, which you use and from the fork of jqGrid (free jqGrid, commercial Guriddo jqGrid JS or an old jqGrid in version <=4.7). I develop free jqGrid fork and I would recommend you to use the current (4.13.3) version of free jqGrid.
i have a paging grid and some function that load a new store to the grid, but when i click on the next page it reverts back to the initial store
the grid:
Ext.define('Pedido', {
extend: 'Ext.data.Model',
fields: [{
name: 'ID',
type: 'int'
}, {
name: 'Nome',
type: 'string'
}, {
name: 'CPF/CNPJ',
type: 'string'
}, {
name: 'Data',
type: 'datetime'
}, {
name: 'TipoPagamento',
type: 'string'
}, {
name: 'StatusPagamento',
type: 'string'
}, {
name: 'TipoPagamento',
type: 'string'
}, {
name: 'Total',
type: 'string'
}],
idProperty: 'ID'
});
var store = Ext.create('Ext.data.Store', {
pageSize: 10,
model: 'Pedido',
remoteSort: true,
proxy: {
type: 'ajax',
url: 'http://localhost:4904/Pedido/ObterPedidosPorInquilino',
reader: {
root: 'Items',
totalProperty: 'TotalItemCount'
}
}
});
var grid = Ext.create('Ext.grid.Panel', {
id: 'grid',
width: 500,
height: 250,
title: 'Array Grid',
store: store,
loadMask: true,
viewConfig: {
id: 'gv',
trackOver: false,
stripeRows: false
},
columns: [{
id: 'gridid',
text: "ID",
dataIndex: 'ID',
width: 50
}],
bbar: Ext.create('Ext.PagingToolbar', {
store: store,
displayInfo: true,
displayMsg: 'Exibindo {0} - {1} de {2}',
emptyMsg: "Nenhum pedido"
}),
renderTo: 'pedidos'
});
store.load({
params: {
start: 0,
limit: 10
}
});
and the function that set the new store
store.load({
params: {
param: newparam
}
});
also, when I call this function, I would like to set the label on the viewing page x of y back to 1, since setting a new store takes you back to the first page
thanks again
I am not sure why you are replacing the store when you page through your data..but notice the paging toolbar is bound to the store as well as the grid itself. If you are going to change the underlying store here you will need to rebind columns, and the paging bar. Try the reconfigure method http://docs.sencha.com/ext-js/4-0/#!/api/Ext.panel.Table-method-reconfigure
Again I think you need to reevaluate your use case and perhaps think of a different way of accomplishing what you are doing.
As you might notice, I'm a newbie in extjs; I have managed to do some stuff myself but the truth is that I don't understand certain things.
I have this tree on the left side, and a content panel with a tab panel on the right side. Basically what I want is to load different options (calling different components) on the tab panel when the user clicks on the tree on the left side. Right now, when the user clicks on the first of the options, it displays the components that are related to that option on the content panel. (I'm sure is not the most elegant way of showing this, but at least for now it works) however, my problem is the fact that the components doesn't seem to load in the right tab, once it loads, even if I change tabs the components stay in the same place.
I have tried using the rbac.tabs.doLayout(); after reading some topics here in the forum, with no success.
I really appreciate the help you guys can give me so i can point in the right direction.
Here is my code:
rbac.userPanel = Ext.create('role.formUserRbac');
rbac.grid = Ext.create('role.gridUserRbac');
rbac.tabsShowPanel = Ext.define('mainPanel',{
extend:'Ext.panel.Panel',
border:'false',
initComponent: function() {
this.callParent();
},
items:[rbac.userPanel,rbac.grid]
});
tabsShowPanel = Ext.create('rbac.tabsShowPanel');
function test(nameTab,des){
rbac.addTab(true,nameTab);
console.log(des);
if (des=='users'){
//console.log(rbac.tabs.addDocked(rbac.testPanel));
rbac.tabs.addDocked(tabsShowPanel)
}
}
Ext.define('treeModel', {
extend: 'Ext.data.Model',
fields: [
{mapping: 'id', name: 'id', type: 'string'},
{mapping: 'text', name: 'text', type: 'string'},
{mapping: 'descripcion', name: 'descripcion', type: 'string'},
]
})
rbac.TreeStore = Ext.create('Ext.data.TreeStore', {
proxy: {
type: 'ajax',
url: 'service.php',
extraParams: {
accion:'loadtree'
},
reader: {
type: 'json',
root: 'nodes',
}
},
autoLoad:true,
sorters: [{
property: 'id',
direction: 'ASC'
},{
property: 'id',
direction: 'ASC'
}],
root: {
id: 0,
expanded: true
},
model:'treeModel'
});
rbac.treePanel = Ext.create('Ext.tree.Panel', {
id: 'tree-panel',
title: 'Navegaci\u00f3n',
region:'west',
split: true,
height: 360,
width: 180,
minSize: 150,
rootVisible: false,
autoScroll: true,
collapsible: true,
collapseMode: 'mini',
store: rbac.TreeStore
});
var currentItem;
rbac.tabs = Ext.create('Ext.tab.Panel', {
resizeTabs: true,
enableTabScroll: true,
defaults: {
autoScroll:true,
bodyPadding: 10
},
items: [{
title: 'Men\u00FA Principal',
iconCls: 'tabs',
closable: false
}]
});
rbac.addTab = function (closable,tabName) {
rbac.tabs.add({
title: tabName,
iconCls: 'tabs',
closable: !!closable
}).show();
//rbac.tabs.doLayout();
}
rbac.treePanel.getSelectionModel().on('select', function(selModel, record) {
if (record.get('leaf')) {
var des = record.data.descripcion;
var nameTab = record.data.text;
test(nameTab,des);
}
});
rbac.contentPanel = {
id: 'content-panel',
region: 'center',
layout: 'card',
margins: '2 5 5 0',
activeItem: 0,
border: false,
items: [rbac.tabs],
};
rbac.panel = Ext.create('Ext.Viewport', {
layout: 'border',
title: 'Ext Layout Browser',
items: [{
xtype: 'box',
id: 'header',
region: 'north',
html: '<img src="images/test.png"/>',
height: 70
},{
layout: 'border',
id: 'layout-browser',
region:'center',
border: false,
split:true,
margins: '2 0 5 5',
width: 275,
minSize: 100,
maxSize: 500,
items: [rbac.treePanel, rbac.contentPanel]
}],
renderTo: Ext.getBody()
});
Solved:
rbac.addTab = function (closable,tabName) {
return rbac.tabs.add({
title: tabName,
iconCls: 'tabs',
closable: !!closable
});
}
function test(nameTab,des){
var newTab = rbac.addTab(true,nameTab);
rbac.tabs.setActiveTab(newTab);
if (des=='users'){
newTab.add(tabsShowPanel)
}
}
I'm trying to update my store and then database after using cellediting and a combobox in a gridpanel to update a record. The update operation.action in the ajax proxy is firing correctly, it just that the store and the grid aren't syncronizing, and the post tab in firebug says my json looks like this: 'data []'. How do I get the store record to create the json and update the record? Thanks for looking at this in advance...
Ext.Loader.setConfig({
enabled: true
});
Ext.Loader.setPath('Ext.ux', '/extjs4/examples/ux');
Ext.require([
'Ext.layout.container.Fit',
'Ext.grid.*',
'Ext.data.*',
'Ext.util.*',
'Ext.panel.*',
'Ext.selection.CellModel',
'Ext.state.*',
'Ext.form.*',
'Ext.ux.CheckColumn']);
Ext.define('Ext.app.HirePlanGrid', {
extend: 'Ext.panel.Panel',
alias: 'widget.hirePlangrid',
hireplanstoreId: 'hireplanstore',
hiremonthstoreId: 'hiremonthstore'
,
renderMonth: function (value, p, record) {
var fkStore = Ext.getStore(this.up('hirePlangrid').hiremonthstoreId);
var rec = fkStore.findRecord("MONTH_ID", value);
//return rec.get("ABBREVIATED_MONTH");
}
,
initComponent: function () {
var rIdx = '';
var cIdx = '';
this.editing = Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 1,
listeners: {
'beforeedit': function (e) {
var me = this;
var allowed = !! me.isEditAllowed;
if (!me.isEditAllowed) Ext.Msg.confirm('confirm', 'Are you sure?', function (btn) {
if (btn !== 'yes') return;
me.isEditAllowed = true;
me.startEditByPosition({
row: e.rowIdx,
column: e.colIdx
});
});
rIdx = e.rowIdx;
cIdx = e.colIdx;
// alert('rIdx= ' + rIdx + ' cIdx = ' + cIdx);
return allowed;
},
'edit': function (e) {
this.isEditAllowed = true;
}
}
});
var objMonthStore = Ext.getStore(this.hiremonthstoreId);
objMonthStore.load();
var objStore = Ext.getStore(this.hireplanstoreId);
objStore.setProxy({
type: 'ajax',
url: 'hireplan.cfc?method=getEmployees'
});
objStore.load();
var onDeleteClick = function (field, value, options) {
// var objPanel = this.down('gridpanel');
var selection = Ext.getCmp('grid').getSelectionModel().getSelection();
// alert(selection);
//var selection = getView().getSelectionModel().getSelection()[r];
if (value) {
//alert(value);
objStore.remove(value);
objStore.sync();
}
};
var onUpdateClick = function (field, value, options) {
alert('field= ' + field + ' value= ' + value + 'options= ' + options);
objStore.update(this.hireplanstoreId, value, 'update', options);
onSync();
};
var onSync = function () {
objStore.sync();
};
Ext.apply(this, {
layout: 'fit',
width: 800,
//height: 1500,
items: [{
xtype: 'grid',
id: 'gridgrid',
//height: 300,
store: objStore,
selModel: {
selType: 'cellmodel'
},
selType: 'rowmodel',
plugins: [this.editing],
// plugins: [cellEditing],
columnLines: true,
viewConfig: {
stripeRows: true
},
//loadMask: true,
disableSelection: true,
columns: [{
header: 'rowid',
hidden: true,
dataIndex: 'ROWID'
}, {
header: 'Indicator',
id: 'chkcolumn',
xtype: 'checkcolumn',
dataIndex: 'CHK_COL',
editor: {
xtype: 'checkbox',
cls: 'x-grid-checkheader-editor'
},
listeners: {
checkchange: function (column, recordIndex, checked) {
alert('checked rindex= ' + recordIndex);
onDeleteClick(column, recordIndex, checked);
//or send a request
}
}
}, {
id: 'employeeid',
header: 'employeeid',
width: 80,
hidden: false,
sortable: true,
dataIndex: 'EMPLOYEEID',
flex: 1
}, {
id: 'NATIONALIDNUMBER',
header: 'NATIONALIDNUMBER',
width: 80,
sortable: true,
dataIndex: 'NATIONALIDNUMBER',
flex: 1
}, {
id: 'MARITALSTATUS',
header: 'MARITALSTATUS',
width: 80,
sortable: true,
dataIndex: 'MARITALSTATUS',
flex: 1
}, {
id: 'PotentialforInsourcingKV',
header: 'Potential for Insourcing',
width: 30,
sortable: true,
dataIndex: 'POTENTIAL_FOR_INSOURCING',
flex: 1,
editor: {
id: 'thiscombo',
xtype: 'combobox',
typeAhead: true,
triggerAction: 'all',
selectOnTab: true,
store: [
['1', 'Yes'],
['0', 'No']
],
lazyRender: true,
listClass: 'x-combo-list-small',
listeners: {
scope: this,
'select': function () {
var selval = Ext.getCmp('thiscombo').getValue();
var row = rIdx;
//alert(selval + ' ' + rIdx);
onUpdateClick('thiscombo', rIdx, selval);
}
}
}
}, {
id: 'ABBREVIATED_MONTH',
header: 'ABBREVIATED_MONTH',
width: 80,
sortable: true,
dataIndex: 'ABBREVIATED_MONTH',
flex: 1,
renderer: this.renderMonth,
field: {
xtype: 'combobox',
store: Ext.getStore(this.hiremonthstoreId),
typeAhead: true,
lazyRender: true,
queryMode: 'local',
displayField: 'ABBREVIATED_MONTH',
valueField: 'MONTH_ID',
listClass: 'x-combo-list-small'
}
}, {
id: 'SALARIEDFLAG',
header: 'SALARIEDFLAG',
width: 80,
sortable: true,
dataIndex: 'SALARIEDFLAG',
flex: 1
}],
features: [{
ftype: 'rowbody'
}]
}]
});
this.callParent(arguments);
}, //initComponent
onSelectChange: function (selModel, selections) {
this.down('#delete').setDisabled(selections.length === 0);
},
viewConfig: {},
});
// JavaScript Document
// JavaScript Document
hireplanstore = Ext.create("Ext.data.Store", {
model: 'HiringPlan',
//autoLoad: true,
//autoSync: true,
buffered: true,
storeId: 'hireplanstore',
remoteFilter: true
,
proxy: {
type: 'ajax',
simpleSortMode: true,
api: {
read: 'hireplan.cfc?method=GetEmployees',
update: 'hireplan.cfc?method=upEmployees',
destroy: 'hireplan.cfc?method=delEmployees'
},
reader: {
type: 'json',
messageProperty: 'message',
successProperty: 'success',
root: 'data'
},
writer: {
type: 'json',
writeAllFields: false,
root: 'data'
},
listeners: {
exception: function (proxy, response, operation) {
Ext.MessageBox.show({
title: 'ERROR from store',
msg: operation.getError(),
icon: Ext.MessageBox.ERROR,
buttons: Ext.Msg.OK
});
}
}
}
});
//hireplanstore.pageSize = 10000;
Ext.define('HiringPlan', {
extend: 'Ext.data.Model',
fields: [{
name: 'ROWID',
type: 'string'
}, {
name: 'EMPLOYEEID',
type: 'string'
}, {
name: 'NATIONALIDNUMBER',
type: 'string'
}, {
name: 'MARITALSTATUS',
type: 'string'
}, {
name: 'GENDER',
type: 'string'
}, {
name: 'POTENTIAL_FOR_INSOURCING',
type: 'integer'
}, {
name: 'ABBREVIATED_MONTH',
type: 'string'
}, {
name: 'SALARIEDFLAG',
type: 'string'
}, {
name: 'CHK_COL',
type: 'bool'
}]
});
In order to update correctly your ajax or rest call have to return an array containing the updated records, even if it's a single record, you have to return it inside an array, a sample json response (for the rest proxy) should be like this:
[{'id': 1, 'name': 'test', 'foo': 'bar'}]