rubyforgood / voices-of-consent

Open source tracking and inventory management application for nonprofit, Voices of Consent (Ruby for Good 2019)
https://voices-of-consent.herokuapp.com
MIT License
38 stars 82 forks source link

Box Request Review: add css and improve tagging experience #141

Open gabezurita opened 5 years ago

gabezurita commented 5 years ago

Pre-cursor PR here: https://github.com/rubyforgood/voices-of-consent/pull/140

maebeale commented 5 years ago

Ideally using Materialize chips (or the same functionality): https://materializecss.com/chips.html

For reference: https://stackoverflow.com/questions/53839487/using-materialize-chip-and-autocomplete-in-ruby-on-rails-form-with-associate

we can prepopulate the tag list with existing tags, but need to set something along the lines of: @tags_json = [{"name" => "testing"}, {"name" => "12344"}].to_json OR @tags_json = @box_request.to_json(only: :tag_list) in the BoxRequestsController#edit

For testing:

<!-- jquery -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <!-- Materialize CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
  <!-- Materialize JavaScript -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
  <!-- Material Icon Webfont -->
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

  <div id="tag_list" name="box_request[tag_list]" class="chips input-field" placeholder="Add tags" data-autocomplete="<%= @tags_json %>"></div>

The following is expecting @tags_json to be associated data w id & name, but that's not what we have, so it will need to be amended. Also, it's set to print vs post...

<script>
    $(document).ready(function() {

        // Cycle through anything with an data-autocomplete attribute
        // Cannot use 'input' as chips must be innitialised on a div
        $("[data-autocomplete]").each(function() {

            var dataJSON = JSON.parse($(this).attr("data-autocomplete"));

            // Prepare array for items and add each
            var items = [];
            var i;
            for (i = 0; i < dataJSON.length; ++i) {
                items[dataJSON[i].name] = null; // Could assign id to image url and grab this later? dataJSON[i].id
            }

            // Check if component needs to be a chips
            if ($(this).hasClass("chips")) {

                // Initialise chips
                // Documentation: https://materializecss.com/chips.html
                $(this).chips({
                    placeholder: $(this).attr("placeholder"),
                    autocompleteOptions: {
                        data: items,
                        limit: Infinity,
                        minLength: 1
                    },
                    onChipAdd: () => {
                        chipChange($(this).attr("id")); // See below
                    },
                    onChipDelete: () => {
                        chipChange($(this).attr("id")); // See below
                    }
                });

                // Tweak the input names, etc
                // This means we can style the code within the view as we would a simple_form input
                $(this).attr("id", $(this).attr("id") + "_wrapper");

                $(this).attr("name", $(this).attr("name") + "_wrapper");

            } else {

                // Autocomplete is much simpler! Just initialise with data
                // Documentation: https://materializecss.com/autocomplete.html
                $(this).autocomplete({
                    data: items,
                });

            }

        });

    });

    function chipChange(elementID) {

        // Get chip element from ID
        var elem = $("#" + elementID);

        // In theory you can get the data of the chips instance, rather than re-parsing it
        var dataJSON = JSON.parse(elem.attr("data-autocomplete"));

        // Remove any previous inputs (we are about to re-add them all)
        elem.children("input[auto-chip-entry=true]").remove();

        // Find the wrapping element
        wrapElement = elem.closest("div[data-autocomplete].chips")

        // Get the input name we need, [] tells Rails that this is an array
        formInputName = wrapElement.attr("name").replace("_wrapper", "") + "[]";

        // Start counting entries so we can add value to input
        var i = 0;

        // Cycle through each chip
        elem.children(".chip").each(function() {

            // Get text of chip (effectively just excluding material icons 'close' text)
            chipText = $(this).ignore("*").text();

            // Get id from original JSON array
            // You should be able to check the initialised Materialize data array.... Not sure how to make that work
            var chipID = findElement(dataJSON, "name", chipText);

            // ?Check for undefined here, will be rejected by Rails anyway...?

            // Add input with value of the selected model ID
            $(this).parent().append('<input value="' + chipID + '"  multiple="multiple" type="hidden" name="' + formInputName + '" auto-chip-entry="true">');

        });

    }

    // Get object from array of objects using property name and value
    function findElement(arr, propName, propValue) {
        for (var i = 0; i < arr.length; i++)
            if (arr[i][propName] == propValue)
                return arr[i].id; // Return id only
        // will return undefined if not found; you could return a default instead
    }

    // Remove text from children, etc
    $.fn.ignore = function(sel) {
        return this.clone().find(sel || ">*").remove().end();
    };

    // Print to console instead of posting
    $(document).on("click", "input[type=submit]", function(event) {

        // Prevent submission of form
        event.preventDefault();

        // Gather input values
        var info = [];
        $(this).closest("form").find("input").each(function() {
            info.push($(this).attr("name") + ":" + $(this).val());
        });

        // Prepare hash in easy to read format
        var outText = "<h6>Output</h6><p>" + info.join("</br>") + "</p>";

        // Add to output if exists, or create if it does not
        if ($("#output").length > 0) {
            $("#output").html(outText);
        } else {
            $("form").append("<div id='output'>" + outText + "</div>");
        }

    });

</script>
maebeale commented 5 years ago

Screen Shot 2019-08-07 at 1 19 42 AM

maebeale commented 5 years ago

Please use this draft PR/branch: https://github.com/rubyforgood/voices-of-consent/pull/182

Would LOVE help on this!

maebeale commented 4 years ago

hello, @gabezurita ! hope you are well! are you still planning to work on this?

gabezurita commented 4 years ago

Hi @maebeale, thanks for calling me out! I've been slammed with work and haven't had much time outside of it, but I'll be on vacation next week! I'll try to find a day to work on it then.