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

Custom widgets for single and multiple choice questions #43

Merged
merged 24 commits into from
Mar 11, 2019
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0e6c057
ADD custom widget type via meta
fkm-adfinis Mar 5, 2019
c6003a7
ADD "Ember Power Select" based component
fkm-adfinis Mar 5, 2019
76b9565
REFACTOR Choice titles to be more generic
fkm-adfinis Mar 5, 2019
ae427d5
UPDATE to upstream
fkm-adfinis Mar 5, 2019
c0a5850
REFACTOR text by moving them to translations
fkm-adfinis Mar 5, 2019
0b0c61d
REFACTOR selected item reference
fkm-adfinis Mar 5, 2019
6dd96e4
REFACTOR choices to use computed property `multiple`
fkm-adfinis Mar 5, 2019
f267ee1
REFACTOR import order
fkm-adfinis Mar 5, 2019
4d596fa
ADD scroll prevention
fkm-adfinis Mar 5, 2019
00878ad
FIX integration test
fkm-adfinis Mar 5, 2019
bc25ffb
CHANGE dropdown to disallow clearing
fkm-adfinis Mar 6, 2019
b00977d
ADD JSDoc style comment for component
fkm-adfinis Mar 6, 2019
2cedc0c
FIX widget not saving the result
fkm-adfinis Mar 6, 2019
e79f949
ADD test for clicking/saving to attain 100% coverage
fkm-adfinis Mar 6, 2019
4faf68c
ADD styling customizations via variables
fkm-adfinis Mar 6, 2019
c11ee1c
Update translations/de-de.yaml
czosel Mar 7, 2019
93d1ccb
CHANGE custom types to not use "default"
fkm-adfinis Mar 7, 2019
85c4542
REFACTOR let to const
fkm-adfinis Mar 7, 2019
afcdab1
REFACTOR fields by moving them to beforeEach
fkm-adfinis Mar 7, 2019
8a329ab
CHANGE condition to use variable type
fkm-adfinis Mar 7, 2019
8f65f5b
REFACTOR properties for more condensed code
fkm-adfinis Mar 7, 2019
3dac987
Merge branch 'feature/custom-widgets' of github.com:fkm/ember-caluma …
fkm-adfinis Mar 7, 2019
e14905b
ADD comment concerning "else path not taken"
fkm-adfinis Mar 11, 2019
2e26037
UPDATE tests for complete code coverage
fkm-adfinis Mar 11, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions addon/components/cf-field/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ export default Component.extend({
type: computed("field.question.__typename", function() {
const typename = get(this, "field.question.__typename");

const meta = get(this, "field.question.meta") || "{}";
const customtype = JSON.parse(meta).widgetType;

return (
(customtype && customtype !== "default" && customtype) ||
(typename && mapping[typename]) ||
typename.replace(/Question$/, "").toLowerCase()
);
Expand Down
89 changes: 89 additions & 0 deletions addon/components/cf-field/input/powerselect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Component from "@ember/component";
import { computed } from "@ember/object";
import { inject as service } from "@ember/service";
import layout from "../../../templates/components/cf-field/input/powerselect";

/**
* Dropdown component for the single and multiple choice question type
*
* @class CfFieldInputPowerSelectComponent
* @argument {Field} field The field for this input type
*/
export default Component.extend({
layout,
tagName: "",
intl: service(),

multiple: computed("field.question.__typename", function() {
return this.get("field.question.__typename").startsWith("Multiple");
}),

choices: computed(
"multiple",
"field.question.{choiceOptions,multipleChoiceOptions}.edges",
function() {
let options = this.get("multiple")
? this.get("field.question.multipleChoiceOptions")
: this.get("field.question.choiceOptions");

return options.edges.map(edge => edge.node);
}
),

selected: computed(
"field.answer.{_valueKey,listValue,stringValue}",
function() {
let key = this.get("field.answer._valueKey");
let path = `field.answer.${key}`;
let answers = this.get(path);
let selection = null;

if (answers !== null) {
selection = this.get("choices").filter(choice => {
return answers.includes(choice.slug);
});

if (key === "stringValue") {
selection = selection[0];
}
}

return selection;
fkm marked this conversation as resolved.
Show resolved Hide resolved
}
),

componentName: computed("multiple", function() {
let name = "power-select";

if (this.get("multiple")) {
name += "-multiple";
}

return name;
fkm marked this conversation as resolved.
Show resolved Hide resolved
}),

searchEnabled: computed("choices", function() {
return this.get("choices").length > 10;
}),

placeholder: computed("multiple", function() {
let suffix = this.get("multiple") ? "multiple" : "single";
let path = `caluma.form.power-select.placeholder-${suffix}`;
return this.get("intl").t(path);
fkm marked this conversation as resolved.
Show resolved Hide resolved
}),

actions: {
change: function(choices) {
let key = this.get("field.answer._valueKey");
let value = null;

if (key === "listValue") {
fkm marked this conversation as resolved.
Show resolved Hide resolved
value = choices.map(choice => choice.slug);
} else if (choices !== null) {
value = choices.slug;
}

this.onSave(value);
}
}
});
13 changes: 12 additions & 1 deletion addon/components/cfb-form-editor/question.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,22 @@ export default Component.extend(ComponentQueryManager, {
]);
}

return yield this.get("apollo").watchQuery(
const questions = yield this.get("apollo").watchQuery(
{
query: formEditorQuestionQuery,
variables: { slug: this.get("slug") },
fetchPolicy: "cache-and-network"
},
"allQuestions.edges"
);

function setWidgetType(question) {
const meta = JSON.parse(question.node.meta);
question.node.widgetType = meta.widgetType;
return question;
}

return A(questions.map(setWidgetType));
}).restartable(),

_getIntegerQuestionInput(changeset) {
Expand Down Expand Up @@ -152,6 +160,9 @@ export default Component.extend(ComponentQueryManager, {
slug: changeset.get("slug"),
isRequired: changeset.get("isRequired"),
isHidden: "false", // TODO: this must be configurable
meta: JSON.stringify({
widgetType: changeset.get("widgetType")
}),
isArchived: changeset.get("isArchived"),
clientMutationId: v4()
},
Expand Down
1 change: 1 addition & 0 deletions addon/gql/fragments/field-question.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ fragment FieldQuestion on Question {
label
isRequired
isHidden
meta
... on TextQuestion {
textMaxLength: maxLength
}
Expand Down
1 change: 1 addition & 0 deletions addon/gql/fragments/question-info.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ fragment QuestionInfo on Question {
label
isRequired
isHidden
meta
isArchived
}
20 changes: 20 additions & 0 deletions addon/templates/components/cf-field/input/powerselect.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{#component componentName
options=choices
selected=selected
disabled=disabled
allowClear=false
preventScroll=true
searchEnabled=searchEnabled
searchField="label"

placeholder=placeholder
loadingMessage=(t "caluma.form.power-select.options-loading")
searchMessage=(t "caluma.form.power-select.options-empty")
searchPlaceholder=(t "caluma.form.power-select.search-placeholder")
noMatchesMessage=(t "caluma.form.power-select.search-empty")

onchange=(action "change")
as |choice|
}}
{{choice.label}}
{{/component}}
12 changes: 12 additions & 0 deletions addon/templates/components/cfb-form-editor/question.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@
required=true
renderComponent=(component "cfb-form-editor/question/options")
}}

{{f.input
name="widgetType"
label=(t "caluma.form-builder.question.widgetType")
required=true
class="uk-flex uk-flex-between uk-flex-column"

type="select"
optionTargetPath="value"
optionLabelPath="label"
options=(array (hash value="default" label="Default") (hash value="powerselect" label="Dropdown"))
fkm marked this conversation as resolved.
Show resolved Hide resolved
}}
{{/if}}

{{f.input
Expand Down
1 change: 1 addition & 0 deletions app/components/cf-field/input/powerselect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "ember-caluma/components/cf-field/input/powerselect";
17 changes: 17 additions & 0 deletions app/styles/_cfb-powerselect.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
$ember-power-select-focus-outline: 0;
$ember-power-select-border-color: #e5e5e5;
$ember-power-select-default-border-radius: 0;
$ember-power-select-line-height: 38px;

$ember-power-select-selected-background: #3799ad;
$ember-power-select-multiple-selection-color: #ffffff;
$ember-power-select-multiple-selection-background-color: $ember-power-select-selected-background;

$ember-power-select-multiple-option-padding: 0 8px;
$ember-power-select-multiple-option-line-height: 32px;
$ember-power-select-multiple-option-border-color: $ember-power-select-border-color;

$ember-power-select-highlighted-color: #ffffff;
$ember-power-select-highlighted-background: lighten($ember-power-select-selected-background, 10%);

@import "ember-power-select";
1 change: 1 addition & 0 deletions app/styles/ember-caluma.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@import "cfb-form-editor/question-list/item";
@import "cfb-loading-dots";
@import "cfb-navigation";
@import "cfb-powerselect";

.cfb-pointer {
cursor: pointer;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"ember-export-application-global": "2.0.0",
"ember-load-initializers": "2.0.0",
"ember-maybe-import-regenerator": "0.1.6",
"ember-power-select": "2.2.2",
"ember-qunit": "4.4.1",
"ember-resolver": "5.0.1",
"ember-source": "3.7.0",
Expand Down
136 changes: 136 additions & 0 deletions tests/integration/components/cf-field/input/powerselect-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { module, test } from "qunit";
import { setupRenderingTest } from "ember-qunit";
import { click, render } from "@ember/test-helpers";
import hbs from "htmlbars-inline-precompile";

module("Integration | Component | cf-field/input/powerselect", function(hooks) {
setupRenderingTest(hooks);

test("it renders (single)", async function(assert) {
assert.expect(1);

this.set("field", {
id: "test",
answer: {
_valueKey: "stringValue",
stringValue: "option-3"
},
question: {
__typename: "ChoiceQuestion",
choiceOptions: {
edges: [
{ node: { slug: "option-1", label: "Option 1" } },
{ node: { slug: "option-2", label: "Option 2" } },
{ node: { slug: "option-3", label: "Option 3" } }
]
}
}
});
fkm marked this conversation as resolved.
Show resolved Hide resolved

await render(hbs`{{cf-field/input/powerselect field=field }}`);

assert.dom(".ember-power-select-trigger").exists();
});

test("it saves on click (single)", async function(assert) {
assert.expect(3);

this.set("field", {
id: "test",
answer: {
_valueKey: "stringValue",
stringValue: "option-3"
},
question: {
__typename: "ChoiceQuestion",
choiceOptions: {
edges: [
{ node: { slug: "option-1", label: "Option 1" } },
{ node: { slug: "option-2", label: "Option 2" } },
{ node: { slug: "option-3", label: "Option 3" } }
]
}
}
});

this.set("onSave", choice => {
this.set("field.answer.stringValue", choice);
});

await render(
hbs`{{cf-field/input/powerselect field=field onSave=onSave }}`
);

assert.dom(".ember-power-select-trigger").exists();
await click(".ember-power-select-trigger");

assert.dom(".ember-power-select-option").exists({ count: 3 });
await click(".ember-power-select-option:first-child");

assert.equal(this.field.answer.stringValue, "option-1");
});

test("it renders (multiple)", async function(assert) {
assert.expect(1);

this.set("field", {
id: "test",
answer: {
_valueKey: "listValue",
listValue: ["option-1", "option-2"]
},
question: {
__typename: "MultipleChoiceQuestion",
multipleChoiceOptions: {
edges: [
{ node: { slug: "option-1", label: "Option 1" } },
{ node: { slug: "option-2", label: "Option 2" } },
{ node: { slug: "option-3", label: "Option 3" } }
]
}
}
});

await render(hbs`{{cf-field/input/powerselect field=field }}`);

assert.dom(".ember-power-select-trigger").exists();
});

test("it saves on click (multiple)", async function(assert) {
assert.expect(3);

this.set("field", {
id: "test",
answer: {
_valueKey: "listValue",
listValue: ["option-1", "option-2"]
},
question: {
__typename: "MultipleChoiceQuestion",
multipleChoiceOptions: {
edges: [
{ node: { slug: "option-1", label: "Option 1" } },
{ node: { slug: "option-2", label: "Option 2" } },
{ node: { slug: "option-3", label: "Option 3" } }
]
}
}
});

this.set("onSave", choices => {
this.set("field.answer.listValue", choices);
});

await render(
hbs`{{cf-field/input/powerselect field=field onSave=onSave }}`
);

assert.dom(".ember-power-select-trigger").exists();
await click(".ember-power-select-trigger");

assert.dom(".ember-power-select-option").exists({ count: 3 });
await click(".ember-power-select-option:first-child");

assert.deepEqual(this.field.answer.listValue, ["option-2"]);
});
});
14 changes: 12 additions & 2 deletions translations/de-de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ dummy:
caluma:
form:
optional: "Optional"

power-select:
placeholder-single: "Wählen Sie eine Option aus"
placeholder-multiple: "Wählen Sie eine oder mehrere Optionen aus"
options-loading: "Lade Optionen..."
options-empty: "Keine Optionen vorhanden"
search-placeholder: "Hier tippen um zu suchen"
search-empty: "Keine Optionen gefunden"

form-builder:
global:
save: "Speichern"
Expand Down Expand Up @@ -38,6 +47,7 @@ caluma:
slug: "Slug"
type: "Typ"
isRequired: "Erforderlich"
widgetType: "Widget Typ"
isArchived: "Archiviert"
type-disabled: "Sobald die Frage erstellt ist kann der Typ nicht mehr geändert werden um korrupte Daten zu verhindern."

Expand All @@ -63,8 +73,8 @@ caluma:
types:
IntegerQuestion: "Ganze Zahl"
FloatQuestion: "Gleitkommazahl"
MultipleChoiceQuestion: "Checkbox"
ChoiceQuestion: "Radio"
MultipleChoiceQuestion: "Mehrfachauswahl"
ChoiceQuestion: "Einzelauswahl"
TextQuestion: "Text"
TextareaQuestion: "Textbereich"

Expand Down
Loading