Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v4 Custom File Input Dynamic "Choose file..." ::after pseudo selector #20813

Closed
niftylettuce opened this issue Sep 30, 2016 · 16 comments
Closed

Comments

@niftylettuce
Copy link

I thought I should share this, maybe we could make this into a JavaScript component that can be toggled. It's a super helpful little JavaScript snippet and CSS that lets you have custom file input boxes with the "Choose file..." and "Browse" buttons, but when the user changes the file input (selects a file) it will show the file name. It should be cross-browser compatible as well. I used lodash for the _, but it could be refactored with whatever @cvrebert and @mdo would use here - I just did this pretty quickly. Hope you don't mind the ES6 syntax.

Example Screenshots:

Before input is selected:

screencapture-localhost-3000-jobs-create-1475233192329

After input is selected:

screencapture-localhost-3000-jobs-create-1475233212895

Usage

JavaScript:

    // handle custom file inputs
    $('body').on('change', 'input[type="file"][data-toggle="custom-file"]', function (ev) {

      const $input = $(this);
      const target = $input.data('target');
      const $target = $(target);

      if (!$target.length)
        return console.error('Invalid target for custom file', $input);

      if (!$target.attr('data-content'))
        return console.error('Invalid `data-content` for custom file target', $input);

      // set original content so we can revert if user deselects file
      if (!$target.attr('data-original-content'))
        $target.attr('data-original-content', $target.attr('data-content'));

      const input = $input.get(0);

      let name = _.isObject(input)
        && _.isObject(input.files)
        && _.isObject(input.files[0])
        && _.isString(input.files[0].name) ? input.files[0].name : $input.val();

      if (_.isNull(name) || name === '')
        name = $target.attr('data-original-content');

      $target.attr('data-content', name);

    });

HTML:

          <label class="custom-file d-block">
            <input data-toggle="custom-file" data-target="#company-logo" type="file" name="company_logo" accept="image/png" class="custom-file-input">
            <span id="company-logo" class="custom-file-control custom-file-name" data-content="Upload company logo..."></span>
          </label>

CSS:

// support custom file inputs
.custom-file-name:after {
  content: attr(data-content) !important;
  position: absolute;
  top: 0px;
  left: 0px;
  display: block;
  height: 100%;
  overflow: hidden;
  padding: 0.5rem 1rem;
}
@bardiharborow
Copy link
Member

Related: #17269.

@mdo
Copy link
Member

mdo commented Jan 8, 2017

I don't think we're going to go with adding JS for this to the core project. Would an example be appropriate, or do we open ourselves to still adding this to our maintenance load.

@bardiharborow
Copy link
Member

bardiharborow commented Jan 21, 2017

I don't imagine the maintenance would be huge. What's more of an issue is that we mention the need for additional JavaScript but don't provide any.

The file input is the most gnarly of the bunch and require additional JavaScript if you’d like to hook them up with functional Choose file… and selected file name text.

@mdo
Copy link
Member

mdo commented Jan 25, 2017

We've done the thing where we recommend code snippets in our docs without making them official plugins. Should we go that route @bardiharborow, or should/could this be the start of some larger potential forms.js file?

@mdo mdo modified the milestone: v4.0.0-beta.2 Apr 22, 2017
@Joyrex
Copy link

Joyrex commented May 4, 2017

@niftylettuce - thank you so much for posting this example! I agree 100% this should be added as an example in the Bootstrap docs, but I think Lodash dependency should be removed (refactor as you suggested). I also think a "remove" button similar to Jasny Bootstrap's file input would be handy as well (as clicking the input, choosing nothing and then clicking Cancel is not as intuitive IMO).

@mdo mdo added this to Misc in v4 Beta 2 May 26, 2017
@mdo mdo removed this from the v4.0.0-beta.2 milestone Jun 18, 2017
@andrefedev
Copy link

Guys, what happened to this? I see that there has not been a final decision to implement this in the core or in the documentation.

@mdo
Copy link
Member

mdo commented Jun 27, 2017

De-prioritized so we can get the beta out.

@boardfish
Copy link

A temporary link here would help at least, but I suppose most folks might find their way here anyway.

@mdo
Copy link
Member

mdo commented Dec 28, 2017

I've changed my mind on this one and would love to see this tackled somehow. I'm curious if we can perhaps start a general set of form JS utilities—custom file input, parent validation classes, etc.

@niftylettuce
Copy link
Author

niftylettuce commented Dec 28, 2017

@mdo I published @ladjs/assets and will be integrated it into Lad (which uses bootstrap 4 latest beta). Not sure if we could use the exported customFileInput event handler or port that somehow to a much more generalized util library. https://github.com/ladjs/assets/blob/02d4dd3f6c3a739e76612e37b6c332b1ea15f97c/src/custom-file-input.js

@brunnopleffken
Copy link

I thought this would be in Beta 4. Put the file name in .custom-file-label (or "2 files", "3 files" for multiple) is needed, otherwise it's just an incomplete feature (some may understand as "here's the CSS now look after yourself with the JS").

I created a JS for some projects using BS4 but it can't handle multiple files...

@subversivo58
Copy link

I really enjoyed this solution I look forward to your progress.

I tried to implement something in Vanilla that observed the Event.RESET... if anyone has an interest you can check in this gist

@blixt
Copy link

blixt commented Feb 16, 2018

Got bitten by this today. It's very strange to me to support styling something into being dysfunctional.

Out of the box, following the documentation, the input box:

  • Does not show any interactivity on hover
  • Does not style focus (use keyboard to navigate form)
  • Does not have an active state (pressed)
  • Does not indicate disabled/enabled state
  • Does not show that a file was picked

When I implemented this I honestly didn't see the notice that said you need custom JS, and when I did a second pass and did notice it I pulled in the bootstrap.min.js code because "obviously that's what they mean", only to find out that the documentation actually means you should implement it yourself with no additional guidance.

If this is being solved in the future, I would suggest in the meantime making the "custom JS" statement more specific and possibly a warning box to avoid this confusion.

@vpratfr
Copy link

vpratfr commented May 3, 2018

Hi,

Here it is as a jQuery plugin with the following enhancements (link to the repo, MIT license, contributions welcome) :

  • It works with the regular markup from the documentation (inserts the custom-file-name span when attached to the file input)
  • it will use the label text when no file is selected
  • I needed to add a z-index to the styles in order to get the name shown (else it was undeneath the label)
(function ($) {
    "use strict";

    const defaults = {
        nameSelector: "custom-file-name",
    };

    let nameElement = null;
    let labelElement = null;
    let inputElement = null;
    let placeholder = null;
    let options = {};

    const methods = {
        init: function (settings) {
            if ($(this).attr("type") !== "file") {
                console.log("The fileInput plugin must be call on the input[type=file] element itself");
                return;
            }

            options = $.extend({}, defaults, settings);

            inputElement = $(this);
            labelElement = inputElement.siblings("label");
            placeholder = labelElement.html();
            nameElement = createNameElement();

            $(this).on("change", onFileChanged);
        },
    };

    const onFileChanged = function (event) {
        const input = inputElement.get(0);
        let fileName = _.isObject(input.files) && _.isObject(input.files[0]) && _.isString(input.files[0].name)
                ? input.files[0].name
                : inputElement.val();

        if (_.isNull(fileName) || fileName === "") {
            labelElement.html(placeholder);
            nameElement.attr("data-content", "");
        }
        else {
            labelElement.html("");
            nameElement.attr("data-content", fileName);
        }
    };

    const createNameElement = function () {
        let element = $("<div />")
                .addClass("custom-file-control custom-file-name")
                .attr("data-content", options.browseText);
        inputElement.parent().append(element);

        return element;
    };

    $.fn.fileInput = function (methodOrOptions) {
        if (methods[methodOrOptions]) {
            return methods[methodOrOptions].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof methodOrOptions === "object" || !methodOrOptions) {
            // Default to "init"
            return methods.init.apply(this, arguments);
        } else {
            $.error("Method " + methodOrOptions + " does not exist on jQuery.fileInput");
        }
    };
}(jQuery));

And the styles as SCSS using BS4 variables :

.custom-file-name:after {
    content  : attr(data-content) !important;
    position : absolute;
    top      : 0;
    left     : 0;
    display  : block;
    height   : 100%;
    overflow : hidden;
    padding  : $input-padding-y $input-padding-x;
    z-index  : 10000;
}

@Ocoolsanni
Copy link

Its probably late for this comment but i just realized there is no need to re-write the custom file upload even for v4-Alpha just update the .custom-file-label using some jquery

$('.custom-file-input').on('change',function(){
	var fileName = $(this).val().replace(/\\/g, '/').replace(/.*\//, '');
	$(".custom-file-label").text(fileName); 
});

@jmsche
Copy link

jmsche commented Jun 27, 2018

Just improved @Ocoolsanni 's snippet a bit, which will handle two more cases:

  • if there are several file inputs on the page
  • if some inputs are dynamically added
$(document).on('change', '.custom-file-input', function () {
    let fileName = $(this).val().replace(/\\/g, '/').replace(/.*\//, '');
    $(this).parent('.custom-file').find('.custom-file-label').text(fileName);
});

Again, thanks @Ocoolsanni for the idea and the base snippet!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests