Stimulus Discourse

I'm looking for a code review on this crud


#1

The code works, though there are a few lines that seem gross to me.

In this line i mount the response to a #id tag, i doubt this is the way to do it in stimulus.

this.element.closest('#notes').innerHTML = response.data;

I think the response needs to target on a parent controller.

<div id="notes">
<form data-controller="createnote">
</form>
</div>

Becomes

<div data-controller="notes">
<form data-controller="createnote">
</form>
</div>

The thing i don’t get working is sending a response to a parent controller. A posibility is getting a controller by a identifier

getControllerByIdentifier(identifier) {
	return this.application.controllers.find(controller => {
		return controller.context.identifier === identifier;
	});
}

The full html

<div id="notes">
	<form action="{{ route('notes.store', $customer) }}"
				method="POST"
				data-controller="create"
				data-create-url="{{ route('notes.store', $customer) }}"
				data-action="submit->create#submit"
	>
		<textarea type="text"
							name="body"
							placeholder="Note Content"
							data-target="create.body"
							value="{{ old( 'body') }}"
							required
		></textarea>
		<button type="submit">Add Note
		</button>
	</form>
	@foreach($customer->monthYearNotes() as $monthYear => $notes)
		<div id="monthYear">
			<h2>{{ $monthYear }}</h2>
			@foreach($notes as $note)
				<div id="note">
					<p>{{ dayMonth($note) }}</p>
					<div id="edit"
							 data-controller="toggle"
					>
						<p data-target="toggle.show"
							 data-action="click->toggle#toggle"
						> {{ $note->body }}</p>
						<div class="hidden"
								 data-target="toggle.hidden"
								 data-action="click->toggle#toggle"
						>
							<form action="{{ route('notes.update', $note) }}"
										method="POST"
										data-controller="edit"
										data-edit-url="{{ route('notes.update', $note) }}"
										data-action="submit->edit#submit"
							>@method('PATCH')
								<textarea type="text"
													name="body"
													placeholder="Note Content"
													data-target="edit.body"
													required
								>{{ old('body') ?: $note->body }}</textarea>
								<button type="submit">Voeg Notitie
								</button>
							</form>
						</div>
						<small>{{ timeAmPm($note) }}</small>
						<button data-action="click->toggle#toggle">
							@svg('icon-136-document-edit')
						</button>
						<form action="{{  route('notes.destroy',$note) }}"
									method="POST"
									data-controller="delete index"
									data-delete-data="{{ $note->id }}"
									data-action="submit->delete#submit"
						>@method('DELETE')
							<button type="submit">
								@svg('icon-26-trash-can')
							</button>
						</form>
					</div>
				</div>
			@endforeach
		</div>
	@endforeach
</div>

The full Js

// create controller
import { NotesController } from "./notes_controller";

export default class extends NotesController {
	static targets = ["body"];

	submit(event) {
		event.preventDefault();
		axios.post(this.data.get('url'), {
			body: this.bodyTarget.value
		}).then(response => {
			this.element.closest('#notes').innerHTML = response.data;
		}).catch(error => console.log(error));
	}
}

// edit controller
import { NotesController } from "./notes_controller";

export default class extends NotesController {
	static targets = ["body"];

	submit(event) {
		event.preventDefault();
		axios.patch(this.data.get('url'), {
			body: this.bodyTarget.value
		}).then(response => {
			this.element.closest('#edit').innerHTML = response.data;
		}).catch(error => console.log(error));
	}
}

// delete controller
import { NotesController } from "./notes_controller";

export default class extends NotesController {
	submit(event) {
		this.deleteNote(event, route('notities.destroy', this.data.get("data")), '#monthYear', '#note');
	}
}

// attempt at to make the functions reusable this is related to delete
import { Controller } from "stimulus";

export class NotesController extends Controller {


	deleteNote(event, route, parent, child) {
		event.preventDefault();
		axios.delete(route);
		if (event.target.closest(parent).querySelectorAll(child).length > 1) {
			event.target.closest(child).remove();
		} else {
			event.target.closest(parent).remove();
		}
	}
}
// Toggle show to edit
import { Controller } from "stimulus";

export default class extends Controller
{
	static targets = [ "show", "hidden" ];

	toggle() {
		this.showTarget.classList.toggle('hidden');
		this.hiddenTarget.classList.toggle('hidden');
	}
}

#4

I’d consider using two controllers, one for the collection and one for individual resources. Here’s a quick sketch:

<div data-controller="notes">
  <form data-action="notes#create">…</form>
  
  @foreach($notes as $note)
    <div data-controller="note">
      <form data-action="note#edit">…</form>
      <form data-action="note#delete">…</form>
    </div>
  @endforeach
</div>
// notes_controller.js
import { Controller } from "stimulus";

export class NotesController extends Controller {
  create(event) {
    event.preventDefault();
    axios.post(/* … */).then(response => {
      this.element.innerHTML = response.data;
    })
  }
}
// note_controller.js
import { Controller } from "stimulus";

export class NoteController extends Controller {
  edit(event) {
    event.preventDefault();
    axios.patch(/* … */).then(response => {
      this.element.outerHTML = response.data;
    })
  }

  delete(event) {
    event.preventDefault();
    axios.delete(/* … */).then(() => {
      this.element.remove();
    })
  }
}

#5

When i implement it with datamaps it returns null

<div data-controller="notes">
  <form data-action="notes#create"
            data-notes-url="Foobar">…</form>
</div>
connect() {
		console.log(this.data.get("url"));
	}

#6

Data Maps
Each Stimulus controller has a data map which lets you access special data attributes on the controller’s element.

<div data-controller="notes" data-notes-url="Foobar">
  <form data-action="notes#create">…</form>
</div>