Skip to content
Permalink
Browse files

feat(navigation): allow customization of the navigation

BREAKING CHANGE: this changes the way a form is used in the host
application. Before, the `cf-navigation` component rendered the whole
navigation and the form in it. Now there is a wrapper component
`cf-content` which yields or renders the form and the navigation. This
way the host app is able to customize where to render the navigation and
the form. Also the host app does not need to pass the `section` and
`subSection` query parameters since they are taken directly from the
router.

Before:
```hbs
{{cf-navigation
  documentId=documentId
  context=context
  section=section
  subSection=subSection
}}
```

After:
```hbs
{{cf-content documentId=documentId context=context}}
```
  • Loading branch information...
anehx committed May 28, 2019
1 parent 8f1cf66 commit fe61ca80968fd809045013e450d84ca57989a1de
@@ -0,0 +1,132 @@
import Component from "@ember/component";
import layout from "../templates/components/cf-content";
import { inject as service } from "@ember/service";
import { computed } from "@ember/object";
import { reads } from "@ember/object/computed";
import { ComponentQueryManager } from "ember-apollo-client";
import { task } from "ember-concurrency";
import getNavigationQuery from "ember-caluma/gql/queries/get-navigation";

/**
* Component to render a form with navigation.
*
* This component can either be used the default way:
* ```hbs
* {{cf-content documentId="some-id"}}
* ```
* Or it can be customized
* ```hbs
* {{#cf-content documentId="some-id" as |content|}}
* <div uk-grid>
* <div class="uk-width-1-2">{{content.navigation}}</div>
* <div class="uk-width-1-2">{{content.form}}</div>
* </div>
* {{/cf-content}}
* ```
*
* @class CfContentComponent
*/
export default Component.extend(ComponentQueryManager, {
layout,

documentStore: service(),
router: service(),

/**
* The ID of the nested document to display the navigation for
* @argument {String} documentId
*/
documentId: null,

/**
* Can be used to pass "context" information from the outside through
* to custom overrides.
*
* @argument {*} context
*/
context: null,

/**
* Form slug of currently visible section
*
* @argument {String} section
* @readonly
*/
section: reads("router.currentRoute.queryParams.section"),

/**
* Form slug of currently visible sub-section
*
* @argument {String} subSection
* @readonly
*/
subSection: reads("router.currentRoute.queryParams.subSection"),

/**
* Whether the form renders in disabled state
*
* @argument {Boolean} disabled
*/
disabled: false,

data: computed("documentId", function() {
const task = this.get("dataTask");

task.perform();

return task;
}),

dataTask: task(function*() {
if (!this.documentId) return null;

return yield this.apollo.watchQuery(
{
query: getNavigationQuery,
variables: { id: window.btoa("Document:" + this.documentId) },
fetchPolicy: "network-only",
context: { headers: this.get("context.headers") }
},
"node"
);
}),

rootDocument: computed("data.lastSuccessful.value", function() {
return (
this.get("data.lastSuccessful.value") &&
this.documentStore.find(this.get("data.lastSuccessful.value"))
);
}),

displayedDocument: computed(
"section",
"subSection",
"rootDocument",
function() {
try {
if (!this.get("rootDocument")) {
return null;
}
if (!this.get("section")) {
return this.get("rootDocument");
}
const section = this.get("rootDocument.fields").find(
field => field.question.slug === this.get("section")
);

if (!this.get("subSection")) {
return section.childDocument;
}
return section.childDocument.fields.find(
field => field.question.slug === this.get("subSection")
).childDocument;
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
return null;
} finally {
window.scrollTo(0, 0);
}
}
)
});
@@ -0,0 +1,8 @@
import Component from "@ember/component";
import layout from "../templates/components/cf-form-wrapper";

export default Component.extend({
layout,

tagName: ""
});
@@ -1,132 +1,7 @@
import Component from "@ember/component";
import layout from "../templates/components/cf-navigation";
import { inject as service } from "@ember/service";
import { computed } from "@ember/object";
import { ComponentQueryManager } from "ember-apollo-client";
import { task } from "ember-concurrency";
import getNavigationQuery from "ember-caluma/gql/queries/get-navigation";

/**
* Component to display a nested form including navigation.
*
* ```hbs
* {{cf-navigation documentId="myDocId" section=section subSection=subSection}}
* ```
*
* @class CfFormComponent
*/
export default Component.extend(ComponentQueryManager, {
layout,
documentStore: service(),

/**
* The ID of the nested document to display the navigation for
* @argument {String} documentId
*/
documentId: null,

/**
* Can be used to pass "context" information from the outside through
* to custom overrides.
*
* @argument {*} context
*/
context: null,

/**
* Form slug of currently visible section
*
* @argument {String} section
*/
section: null,

/**
* Form slug of currently visible sub-section
*
* @argument {String} subSection
*/
subSection: null,

/**
* Renders the forms disabled.
* @argument {Boolean} disabled
*/
disabled: false,

_currentDocumentId: null,

async didReceiveAttrs() {
this._super(...arguments);
if (this.documentId && this.documentId !== this._currentDocumentId) {
await this.data.perform();
this._currentDocumentId = this.documentId;
}
},

data: task(function*() {
return yield this.apollo.watchQuery(
{
query: getNavigationQuery,
variables: { id: window.btoa("Document:" + this.documentId) },
fetchPolicy: "network-only",
context: { headers: this.get("context.headers") }
},
"node"
);
}),

rootDocument: computed("data.lastSuccessful.value", function() {
return (
this.get("data.lastSuccessful.value") &&
this.documentStore.find(this.get("data.lastSuccessful.value"))
);
}).readOnly(),

displayedDocument: computed(
"section",
"subSection",
"rootDocument",
function() {
try {
if (!this.get("rootDocument")) {
return null;
}
if (!this.get("section")) {
return this.get("rootDocument");
}
const section = this.get("rootDocument.fields").find(
field => field.question.slug === this.get("section")
);

if (!this.get("subSection")) {
return section.childDocument;
}
return section.childDocument.fields.find(
field => field.question.slug === this.get("subSection")
).childDocument;
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
return null;
} finally {
window.scrollTo(0, 0);
}
}
),

fields: computed("rootDocument", function() {
const isFormQuestion = field =>
field.question.__typename === "FormQuestion";
return this.getWithDefault("rootDocument.fields", [])
.filter(isFormQuestion)
.map(field => {
field.set(
"navSubFields",
field
.getWithDefault("childDocument.fields", [])
.filter(isFormQuestion)
);
return field;
});
})
layout
});
@@ -6,6 +6,7 @@ import jexl from "jexl";
import { atob } from "ember-caluma/helpers/atob";
import { inject as service } from "@ember/service";
import { intersects } from "ember-caluma/utils/jexl";
import { filterBy } from "@ember/object/computed";

const getParentState = childStates => {
if (childStates.every(state => state === "untouched")) {
@@ -198,6 +199,8 @@ export default EmberObject.extend({

fields: computed(() => []).readOnly(),

visibleFields: filterBy("fields", "hidden", false),

childDocuments: computed(
"fields.{[],@each.hidden,childDocument}",
function() {
@@ -1,4 +1,4 @@
import EmberObject, { computed } from "@ember/object";
import EmberObject, { computed, getWithDefault } from "@ember/object";
import { equal, not, empty, reads } from "@ember/object/computed";
import { inject as service } from "@ember/service";
import { assert } from "@ember/debug";
@@ -194,6 +194,27 @@ export default EmberObject.extend(Evented, {
*/
hidden: reads("question.hidden"),

/**
* The type of the question
*
* @property {String} questionType
* @accessor
*/
questionType: reads("question.__typename"),

visibleInNavigation: computed(
"hidden",
"questionType",
"childDocument.visibleFields",
function() {
return (
!this.hidden &&
this.questionType === "FormQuestion" &&
getWithDefault(this, "childDocument.visibleFields", []).length > 0
);
}
),

/**
* The error messages on this field.
*
@@ -0,0 +1,23 @@
{{#if data.isRunning}}
<div class="uk-text-center">{{uk-spinner ratio=2}}</div>
{{else}}
{{#let (hash
rootDocument=rootDocument
displayedDocument=displayedDocument
navigation=(component "cf-navigation" rootDocument=rootDocument section=section subSection=subSection)
form=(component "cf-form-wrapper" document=displayedDocument context=context disabled=disabled)
) as |content|}}
{{#if hasBlock}}
{{yield content}}
{{else if rootDocument.childDocuments.length}}
<div uk-grid>
<div class="uk-width-1-1 uk-width-1-3@m">{{content.navigation}}</div>
<div class="uk-width-1-1 uk-width-2-3@m">
{{content.form}}
</div>
</div>
{{else}}
{{content.form}}
{{/if}}
{{/let}}
{{/if}}
@@ -0,0 +1 @@
{{component (get-widget document.field default="cf-form") document=document context=context disabled=disabled}}
@@ -1,10 +1,8 @@
{{#if (not field.question.hidden)}}
{{#link-to (query-params section=section subSection=_subSection)}}
<div class="uk-flex uk-flex-between uk-flex-middle">
<div>{{label}}</div>
<span
class="uk-flex-none camac-nav-module-icon camac-nav-module-icon-{{state}}">
</span>
</div>
{{/link-to}}
{{/if}}
{{#link-to (query-params section=section subSection=_subSection)}}
<div class="uk-flex uk-flex-between uk-flex-middle">
<div>{{label}}</div>
<span
class="uk-flex-none camac-nav-module-icon camac-nav-module-icon-{{state}}">
</span>
</div>
{{/link-to}}

0 comments on commit fe61ca8

Please sign in to comment.
You can’t perform that action at this time.