Skip to content

Commit

Permalink
add allow custom option to multiple choice
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilReinking committed Dec 28, 2022
1 parent 5ada89c commit 55ef36d
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 18 deletions.
8 changes: 3 additions & 5 deletions app/Http/Controllers/Api/FormBlockInteractionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ public function create(Request $request, FormBlock $block)
))],
]);

$interaction = new FormBlockInteraction([
'type' => $request->input('type'),
]);
$interaction = new FormBlockInteraction($request->only('type', 'name', 'is_editable', 'is_disabled'));

$block->formBlockInteractions()->save($interaction);

Expand All @@ -38,12 +36,12 @@ public function update(Request $request, FormBlockInteraction $interaction)
switch ($interaction->type) {
case FormBlockInteractionType::button:
$request->validate([
'label' => 'min:1',
'label' => 'exclude_if:is_editable,false|min:1',
]);
break;
}

$interaction->fill($request->only(['label', 'message', 'uuid', 'options']));
$interaction->fill($request->only(['label', 'message', 'uuid', 'options', 'is_editable', 'is_disabled', 'name']));
$interaction->save();

return response()->json($interaction, 200);
Expand Down
2 changes: 2 additions & 0 deletions app/Models/FormBlockInteraction.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class FormBlockInteraction extends Model
'form_block_id' => 'integer',
'type' => FormBlockInteractionType::class,
'options' => 'array',
'is_editable' => 'boolean',
'is_disabled' => 'boolean',
];

protected static function boot()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('form_block_interactions', function (Blueprint $table) {
$table->after('type', function (Blueprint $table) {
$table->string('name')->nullable();
$table->boolean('is_editable')->default(true);
$table->boolean('is_disabled')->default(false);
});
});
}
};
4 changes: 3 additions & 1 deletion resources/js/api/interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import handler from "./handler";

export function callCreateFormBlockInteraction(
id: number,
type: FormBlockInteractionModel["type"]
type: FormBlockInteractionModel["type"],
attributes?: Partial<FormBlockInteractionModel>
): Promise<AxiosResponse<FormBlockInteractionModel>> {
return new Promise(async (resolve, reject) => {
try {
const response = await handler.post(
window.route("api.interactions.create", { block: id }),
{
type,
...attributes,
}
);

Expand Down
57 changes: 50 additions & 7 deletions resources/js/components/Factory/Main/ConfigureClicks.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<EmptyState
v-if="currentInteractions?.length === 0"
v-if="editableInteractions?.length === 0"
title="Nothing here yet."
description="Add your first option by clicking on the button below."
/>
Expand All @@ -13,7 +13,7 @@
orientation="vertical"
@drop="onDrop"
>
<Draggable v-for="(item, index) in currentInteractions" :key="item.id">
<Draggable v-for="(item, index) in editableInteractions" :key="item.id">
<ClickInteraction
v-bind="{ item, index }"
:key="`${item.id}-${index}`"
Expand All @@ -26,6 +26,17 @@
</Draggable>
</Container>
<div class="mb-3 flex justify-between pt-3">
<D9Label label="Allow Custom Response" />
<D9Switch
label=""
v-model="useCustomResponse"
onLabel="yes"
offLabel="no"
@change="updateCustomResponseSetting"
/>
</div>
<div class="mt-4">
<D9Button
label="Add new option"
Expand All @@ -41,16 +52,30 @@
<script setup lang="ts">
import { useWorkbench } from "@/stores";
import { D9Button } from "@deck9/ui";
import { ref, nextTick } from "vue";
import { D9Button, D9Label, D9Switch } from "@deck9/ui";
import { ref, nextTick, computed } from "vue";
import ClickInteraction from "./Interactions/ClickInteraction.vue";
import { Container, Draggable } from "vue3-smooth-dnd";
import EmptyState from "@/components/EmptyState.vue";
import { storeToRefs } from "pinia";
import { useKeyboardNavigation } from "@/components/Factory/utils/useKeyboardNavigation";
import useActiveInteractions from "../Shared/useActiveInteractions";
const workbench = useWorkbench();
const { currentInteractions } = storeToRefs(workbench);
const { activeInteractions, editableInteractions } = useActiveInteractions(
workbench.block
);
const otherOptionInteractionName = "other_response";
const otherOptionInteraction = computed(() => {
return activeInteractions.value?.find((interaction) => {
return interaction.name === otherOptionInteractionName;
});
});
const useCustomResponse = ref(
otherOptionInteraction.value?.is_disabled === false
);
const isCreatingInteraction = ref(false);
Expand Down Expand Up @@ -80,14 +105,32 @@ const createClickInteraction = async () => {
}
};
const updateCustomResponseSetting = async () => {
// check if we have already created the interaction used for other option
if (!otherOptionInteraction.value) {
// create the interaction
await workbench.createInteraction("button", {
name: otherOptionInteractionName,
is_editable: false,
is_disabled: !useCustomResponse.value,
});
} else {
// update the interaction
await workbench.updateInteraction({
id: otherOptionInteraction.value.id,
is_disabled: !useCustomResponse.value,
});
}
};
const {
bindTemplateRefsForTraversables,
focusNextItem,
focusNextItemSoft,
focusPreviousItem,
focusLastItem,
focusNeighborItem,
} = useKeyboardNavigation(currentInteractions, async () => {
} = useKeyboardNavigation(activeInteractions, async () => {
await createClickInteraction();
});
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useForm } from "@/stores/form";

export default function (block: FormBlockModel | null): {
activeInteractions: ComputedRef<FormBlockInteractionModel[] | undefined>;
editableInteractions: ComputedRef<FormBlockInteractionModel[] | undefined>;
} {
const activeInteractions = computed(() => {
const store = useForm();
Expand All @@ -21,7 +22,14 @@ export default function (block: FormBlockModel | null): {
return sortBy(interactions, ["sequence"]);
});

const editableInteractions = computed(() => {
return activeInteractions.value?.filter((interaction) => {
return interaction.is_editable;
});
});

return {
activeInteractions,
editableInteractions,
};
}
4 changes: 2 additions & 2 deletions resources/js/components/Factory/Sidebar/Block.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
</div>

<BlockInteraction
v-for="(interaction, index) in activeInteractions"
v-for="(interaction, index) in editableInteractions"
v-bind="{ interaction, index }"
:key="interaction.id"
/>
Expand Down Expand Up @@ -84,7 +84,7 @@ const props = defineProps<{
provide("block", props.block);
const { activeInteractions } = useActiveInteractions(props.block);
const { editableInteractions } = useActiveInteractions(props.block);
const isActive = computed((): boolean => {
return workbench.block && workbench.block.id === props.block.id
Expand Down
6 changes: 4 additions & 2 deletions resources/js/stores/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ export const useWorkbench = defineStore("workbench", {
},

async createInteraction(
type: FormBlockInteractionModel["type"]
type: FormBlockInteractionModel["type"],
attributes?: Partial<FormBlockInteractionModel>
): Promise<FormBlockInteractionModel | undefined> {
if (!this.block) {
return;
Expand All @@ -125,7 +126,8 @@ export const useWorkbench = defineStore("workbench", {
try {
const response = await callCreateFormBlockInteraction(
this.block?.id,
type
type,
attributes
);

if (response.status === 201) {
Expand Down
3 changes: 3 additions & 0 deletions resources/js/types/models.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ type FormBlockInteractionSettings = {

interface FormBlockInteractionModel extends BaseModel {
type: FormBlockInteractionType;
name: string | null;
is_editable: boolean | null;
is_disabled: boolean | null;
label: string | null;
options: FormBlockInteractionSettings;
message: string | null;
Expand Down
69 changes: 68 additions & 1 deletion tests/Feature/Forms/Interactions/InteractionsTestingContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ public function can_delete_an_interaction_of_this_type()
/** @test */
public function can_save_json_option_object_to_interaction()
{
$this->withoutExceptionHandling();
$interaction = FormBlockInteraction::factory()->create([
'type' => $this->getInteractionType(),
]);
Expand All @@ -103,4 +102,72 @@ public function can_save_json_option_object_to_interaction()
'max_chars' => 250,
], $interaction->fresh()->options);
}

/** @test */
public function an_interaction_can_be_made_not_editable()
{
$interaction = FormBlockInteraction::factory()->create([
'type' => $this->getInteractionType(),
]);

$this->assertTrue($interaction->fresh()->is_editable);

$this->actingAs($interaction->formBlock->form->user)
->json('post', route('api.interactions.update', $interaction->id), [
'is_editable' => false,
])
->assertSuccessful();

$this->assertFalse($interaction->fresh()->is_editable);
}

/** @test */
public function an_interaction_can_be_made_disabled()
{
$interaction = FormBlockInteraction::factory()->create([
'type' => $this->getInteractionType(),
]);

$this->assertFalse($interaction->fresh()->is_disabled);

$this->actingAs($interaction->formBlock->form->user)
->json('post', route('api.interactions.update', $interaction->id), [
'is_disabled' => true,
])
->assertSuccessful();

$this->assertTrue($interaction->fresh()->is_disabled);
}

/** @test */
public function an_interaction_can_be_named()
{
$interaction = FormBlockInteraction::factory()->create([
'type' => $this->getInteractionType(),
]);

$this->actingAs($interaction->formBlock->form->user)
->json('post', route('api.interactions.update', $interaction->id), [
'name' => 'other_option',
])
->assertSuccessful();

$this->assertEquals('other_option', $interaction->fresh()->name);
}

/** @test */
public function label_can_be_null_when_interaction_is_not_editable()
{
$interaction = FormBlockInteraction::factory()->create([
'type' => $this->getInteractionType(),
'is_editable' => false,
]);

$this->actingAs($interaction->formBlock->form->user)
->json('post', route('api.interactions.update', $interaction->id), [
'label' => null,
'is_editable' => false,
])
->assertSuccessful();
}
}

0 comments on commit 55ef36d

Please sign in to comment.