AjaxSubmit not uploading formData using my Custom Route



  • Re: Documentation on making ajaxify.go work for custom routes?

    Hello:
    I am trying to create an alternate route, very similar to topic thumbnail upload route (/api/topic/thumb/upload) for uploading tiny icons. It appears that at server side, my controller does not receive the formData from ajaxSubmit call on the client side.

    Currently, my plugin is using existing hook filter:uploadStored but it is adding significant complexity to my logic since I am required to preserve uploadThumb functionality. Besides the absence of image resize hook for bypassing that step caused a heck of work around.

    Instrumentation shows the data flow correctly on the client side to AjaxSubmit and call is received at server endpoint but no data is uploaded.

    Route definition:

    plugin.init = function(params, callback) {
        var app = params.router;
        var middleware = params.middleware;
        var multipart = require('connect-multiparty');
        var multipartMiddleware = multipart();
        var middlewares = [middleware.maintenanceMode, multipartMiddleware, middleware.validateFiles, middleware.applyCSRF];
    
        var controller = uploadsController.uploadIcon;
        //    routeHelpers.setupPageRoute(app, '/icon/upload',middleware ,middlewares , uploadIcon);   
        var name = '/icon/upload'; 
        app.post(name, middlewares, controller);
        app.post('/api' + name,  controller);
    
        app.get('/admin/icons', middleware.admin.buildHeader, renderAdmin);
        app.get('/api/admin/icons', renderAdmin);
    
        callback();
    };
    

    My controller for uploadIcon has almost identical implementation as uploadsController.uploadThumb at this point:

    uploadsController.uploadIcon= function(req, res, next) {
      console.log('\n\n\nrequest object:',req);    // I get request object without formData values
      console.log('... files:',req.files);         //undefined
      console.log("... uuid:!%o",req.uuid);        //undefined
      console.log("...formData:!%o",req.formData); //undefined
    
    // don't execute beyond this line
    var files = req.files.files;
    
    }
    
    

    My change handler icon upload form container is identical to Thumb upload as well except the change handler calls icon upload function :

    //uploadTopicIcon({files: files, post_uuid: post_uuid, route: '/api/topic/thumb/upload', formData: fd}); // everything works 
    uploadTopicIcon({files: files, post_uuid: post_uuid, route: '/api/icon/upload', formData: fd}); // no data is sent to server
    

    This function is also almost identical to thumb upload except the names of the forms has changed:

    function uploadTopicIcon(params) {
    		var post_uuid = params.post_uuid,
    			postContainer = $('#cmp-uuid-' + post_uuid),
    			spinner = postContainer.find('.topic-icon-spinner'),
    			iconForm = postContainer.find('#iconForm.');
    
    		iconForm.attr('action', config.relative_path + params.route);
    
    		iconForm.off('submit').submit(function() {
    			spinner.removeClass('hide');
    
    			uploads.inProgress[post_uuid] = uploads.inProgress[post_uuid] || [];
    			uploads.inProgress[post_uuid].push(1);
    
    			$(this).ajaxSubmit({
    				headers: {
    					'x-csrf-token': config.csrf_token
    				},
    				formData: params.formData,
    				error: onUploadError,
    				success: function(uploads) {
    					postContainer.find('#topic-icon-url').val((uploads[0] || {}).url || '').trigger('change');
    				},
    				complete: function() {
    					uploads.inProgress[post_uuid].pop();
    					spinner.addClass('hide');
    				}
    			});
    			return false;
    		});
    		iconForm.submit();
    	}
    

    As I mentioned I have developed my plugin using the hooks but it is very intrusive and I really wish to get the custom route work. Any pointers appreciated since I have been pulling my hair for a few days now.

    Thanks in advance


  • Global Moderator

    Hi, I recently had a similar experience working on the upcoming emoji plugin rework. I used multer instead of connect-multiparty.

    Here's the backend code for it: https://github.com/NodeBB/nodebb-plugin-emoji/blob/master/lib/controllers.ts#L58
    It's typescript, but it shouldn't be too hard to read.

    The frontend code is React, but here's the rundown:

    I used a custom button because the image file upload button looks ugly to me, this allows for more customization.

    <button type="button" className="btn btn-default">Upload image</button>
    <form 
      action="{window.config.relative_path}/api/admin/plugins/emoji/upload"
      method="post" 
      encType="multipart/form-data"
      style="display: none;"
      id="image-form"
    >
      <input
        type="file"
        name="emojiImage"
        accept="image/*"
        id="image-input"
      />
      <input 
        type="hidden"
        name="fileName"
        id="file-name-input"
      />
    </form>
    

    Then I generate a unique filename on the upload, which is used server-side

    const uploadImageButton = document.getElementById('upload-image');
    const imageForm = document.getElementById('image-form');
    const imageInput = document.getElementById('image-input');
    const fileNameInput = document.getElementById('file-name-input');
    
    const uploadImage = () => {
      imageInput.click();
      
      $(imageInput).one('change', () => {
        if (!imageInput.files.length) {
          return;
        }
    
        const fileName = `${window.utils.generateUUID()}-${imageInput.files[0].name}`;
        fileNameInput.value = fileName;
    
        $(imageForm).ajaxSubmit({
          success: () => {
            // do stuff
            imageInput.value = '';
          },
          error: () => {
            const err = Error('Failed to upload file');
            console.error(err);
            window.app.alertError(err);
            imageInput.value = '';
          },
        });
      });
    };
    
    uploadImageButton.addEventListener('click', uploadImage, false);
    


  • @pitaj Thank you! I will give it a try.

    Much appreciated.



  • @pitaj Thanks for the very helpful post. For the benefit of others who stumble upon same issue using multi-part middleware, the fix for issue in my original post was simply as follows:

    plugin.init = function(params, callback) {
        var app = params.router;
        var middleware = params.middleware;
        var multipart = require('connect-multiparty');
        var multipartMiddleware = multipart();
    
        var middlewares = [multipartMiddleware, middleware.applyCSRF];
    
        var controller = uploadsController.uploadIcon;
        var name = '/icon/upload'; 
    
        app.post(name, middlewares, controller);
        app.post('/api' + name,  middlewares,  controller);
    
        app.get('/admin/icons', middleware.admin.buildHeader, renderAdmin);
        app.get('/api/admin/icons', renderAdmin);
    
        callback();
    };
    
    Thanks again for your support.

 

| |