statamic / workshop

Create and edit entries, pages, and globals on the front-end of your site without the control panel.
https://statamic.com/marketplace/addons/workshop
3 stars 2 forks source link

Please advise how you use Grid fieldtype on frontend Workshop form. #16

Closed bgarrant closed 6 years ago

bgarrant commented 6 years ago

Is your feature request related to a problem? Please describe. I have a site that we need to allow a client to add data to a grid from a frontend Workshop form. Looks like people have been able to do this before https://statamic.com/forum/1850-grid-data-not-saving-from-front-end-prof, https://statamic.com/forum/1357-edit-grid-fields-or-table-field-on-the-f.

Describe the solution you'd like We need to know how to add a grid fieldtype to a Workshop frontend form. There must be a way this can be done already. Something like you see in the backend where you can just add rows.

https://cl.ly/320t0L3h0U0Z

Describe alternatives you've considered I am not great at JS but I am sure there must be a way to do this.

Additional context

danielfowler commented 6 years ago

@bgarrant You can use nested array inputs to save data in the same format as what your grid fieldtype will allow.

Here's a CodePen that shows how I recreated one of my grid fieldsets using Workshop. The resultant YAML looks like this:

crew:
  1:
    name: John Doe
    role: Director
    responsibilities: Make the show awesome.
  2:
    name: Jane Crow
    role: Choreographer
    responsibilities: Teach people to dance.
danielfowler commented 6 years ago

The trick is making sure each row's key is unique.

Also, if this is a form to edit content, you can use Statamic's {{ zero_index }} to pre-assign any pre-existing keys, a la <input name="crew[{{ zero_index }}][name]"/> and then assign your starting key in the template directly, a la:

{{ some_grid }}
  <div class="flex">
      <b class="add">Add</b>
      <input name="crew[{{ zero_index }}][name]" value="{{ name }}">
      <input name="crew[{{ zero_index }}][role]" value="{{ role }}">
      <input name="crew[{{ zero_index }}][responsibilities]" value="{{ responsibilities }}">
      <b class="remove">Remove</b>
  </div>

  {{ if last }}
    <script>var key = {{ index }}</script>
  {{ /if }}
{{ /some_grid }}

edit: if editing content, you'll also have to assign event listeners to any add/remove buttons that pre-exist.

bgarrant commented 6 years ago

@danielfowler this is great. Do you know how to toggle the add and remove row for the ADD and EDIT forms? I am horrible at JS. Thanks

danielfowler commented 6 years ago

I don't follow what you mean. If you're trying to hide the add/remove buttons, you can use CSS.

bgarrant commented 6 years ago

I am laying out the data in a table. In my ADD form I need a way to add table rows and remove them. I tried this method but there must be a better way https://smarttutorials.net/add-remove-table-rows-jquery/. You can see more details in this thread https://statamic.slack.com/archives/C04CM7HA9/p1532796713000064 if you use Slack

bgarrant commented 6 years ago

Does your method handle the ADD and DELETE if rows? I do not have to use a table at all.

danielfowler commented 6 years ago

No, you don't have to use a table. A table for layout is never advised. My method does handle adding and deleting rows, without jQuery. I hate depending on libraries. Vanilla JS ftw.

bgarrant commented 6 years ago

@danielfowler so all I need is your code and it will add and remove rows? What else do I need on the edit form? Just the ID of the entry and it will render the existing grid and allow add and remove also?

bgarrant commented 6 years ago

I asked this in Slack also, but how can I add an Edit event listener to this @danielfowler ? That is all I need to complete this I think. Thanks

danielfowler commented 6 years ago

Easy! I think... Try <b class="add" onclick="addEmptyRow(this.parentNode)">Add</b> and <b class="remove" onclick="removeRow(this.parentNode)">Remove</b>

danielfowler commented 6 years ago

in your {{ workshop:entry:edit }} HTML template

bgarrant commented 6 years ago

The remove works but ADD is not. Typo maybe @danielfowler ?

            {{ event_schedule }}
              <div class="flex">
                  <b class="add" onclick="addEmptyRow(this.parentNode)">Add</b>
                  <input name="event_schedule[{{ zero_index }}][schedule_day]" value="{{ schedule_day }}">
                  <input name="event_schedule[{{ zero_index }}][schedule_time]" value="{{ schedule_time }}">
                  <input name="event_schedule[{{ zero_index }}][schedule_details]" value="{{ schedule_details }}">
                  <b class="remove" onclick="removeRow(this.parentNode)">Remove</b>
              </div>

              {{ if last }}
                <script>var key = {{ index }}</script>
              {{ /if }}
            {{ /event_schedule }}
bgarrant commented 6 years ago

I may have it now. I will post back if any issues. Thanks so much!

danielfowler commented 6 years ago

I forked my original CodePen to show it working with pre-existing content.

bgarrant commented 6 years ago

@danielfowler The add part only is working as this <b class="add" onclick="addEmptyRow()">Add Day</b>

            {{ event_schedule }}
              <div class="flex">
                  <b class="add" onclick="addEmptyRow()">Add Day</b>
                  <input name="event_schedule[{{ zero_index }}][schedule_day]" value="{{ schedule_day }}">
                  <input name="event_schedule[{{ zero_index }}][schedule_time]" value="{{ schedule_time }}">
                  <input name="event_schedule[{{ zero_index }}][schedule_details]" value="{{ schedule_details }}">
                  <b class="remove" onclick="removeRow(this.parentNode)">Remove</b>
              </div>

              {{ if last }}
                <script>var key = {{ index }}</script>
              {{ /if }}
            {{ /event_schedule }}
danielfowler commented 6 years ago

Your addEmptyRow() needs to be addEmptyRow(this.parentNode)

bgarrant commented 6 years ago

@danielfowler with that it fails to add row. here is what I have. Thank agin for helping. I am close

<script>
    var container = document.querySelector('#container')
    var key = 0

    function addEmptyRow(el) {
      key++ // increment the key so it's always unique
      // recreate a new grid row using HTML
      var blankHTML =
      '<b class="add">Add</b>' +
      '<input name="event_schedule[' + key + '][schedule_day]" placeholder="Day">' +
      '<input name="event_schedule[' + key + '][schedule_time]" placeholder="Time">' +
      '<input name="event_schedule[' + key + '][schedule_details]" placeholder="Details">' +
      '<b class="remove">Remove</b>';
      var newNode = document.createElement('div') // create DOM node for row
      newNode.classList.add('flex') // arbitrary class names
      newNode.innerHTML = blankHTML // fill the row with its HTML 

      // add/remove buttons allow user to add and remove rows
      // a more advanced JS programmer might enable drag & drop
      var addButton = newNode.querySelector('.add')
      var removeButton = newNode.querySelector('.remove')

      // add event listeners to the new add/remove buttons
      addButton.onclick = function(e) {addEmptyRow(newNode)}
      removeButton.onclick = function(e) {removeRow(newNode)}
      var insertedNode = container.insertBefore(newNode,el)
    }

    function removeRow(el) {
      el.parentNode.removeChild(el)
    }

    window.addEventListener('load',function(e) {
      container.innerHTML = '' // clean out the container by default
    })
  </script>
            <div id="container"></div>
            {{ event_schedule }}
              <div class="flex">
                <b class="add" onclick="addEmptyRow(this.parentNode)">Add Day</b>
                  <input name="event_schedule[{{ zero_index }}][schedule_day]" value="{{ schedule_day }}">
                  <input name="event_schedule[{{ zero_index }}][schedule_time]" value="{{ schedule_time }}">
                  <input name="event_schedule[{{ zero_index }}][schedule_details]" value="{{ schedule_details }}">
                  <b class="remove" onclick="removeRow(this.parentNode)">Remove</b>
              </div>

              {{ if last }}
                <script>var key = {{ index }}</script>
              {{ /if }}
            {{ /event_schedule }}
</div
bgarrant commented 6 years ago

Remove works perfect. Add is not making a row

danielfowler commented 6 years ago

Put your {{ events_schedule }} tag pair inside the <div id="container"></div>

danielfowler commented 6 years ago

I've updated my CodePen since I first posted it. See it here: https://codepen.io/danielfowler/pen/GBOyjG

bgarrant commented 6 years ago

Thanks so much @danielfowler . Think I got it now. :)