I am using Form.io v3.27.1, and I am trying to create a custom layout component - specifically an accordion - and I'm using the concepts provided in the CheckMatrix component example for the most part.
I am able to make the accordion component show in the toolbox, and I'm able to drag it onto the form, configure it with a custom edit form etc. I can save it and it renders a Bootstrap themed accordion perfectly.
What it does NOT do however, is allow me to drag and drop other components into the content area similar to the behavior of other layout components (i.e. Tabs, Columns, Fieldset etc.).
I assume by skimming through the source code of the other layout controls that I need to extend NestedComponent in lieu of BaseComponent, but I've not yet been able to make that work.
I feel like I am overlooking something small. I just can't seem to figure out how to render a layout component that accepts other Form.io components as children.
Anyone have a working example or suggestions I can try to get this working? I appreciate your help in advance!
import BaseComponent from 'formiojs/components/base/Base';
import NestedComponent from 'formiojs/components/nested/NestedComponent';
import Components from 'formiojs/components/Components';
import * as editForm from './Accordian.form';
export default class AccordionComponent extends BaseComponent {
/**
* Define what the default JSON schema for this component is. We will derive from the BaseComponent
* schema and provide our overrides to that.
* #return {*}
*/
static schema() {
return BaseComponent.schema({
type: 'accordion',
label: 'Sections',
input: false,
key: 'accordion',
persistent: false,
components: [{
label: 'Section 1',
key: 'section1',
components: []
}]
});
}
/**
* Register this component to the Form Builder by providing the "builderInfo" object.
*/
static get builderInfo() {
return {
title: 'Accordion',
group: 'custom',
icon: 'fa fa-tasks',
weight: 70,
schema: AccordionComponent.schema()
};
}
/**
* Tell the renderer how to build this component using DOM manipulation.
*/
build() {
this.element = this.ce('div', {
class: `form-group formio-component formio-component-accordion ${this.className}`
}, [
this.ce('app-formio-accordian', {
components: JSON.stringify(this.component.components)
})
]);
}
elementInfo() {
return super.elementInfo();
}
getValue() {
return super.getValue();
}
setValue(value) {
super.setValue(value);
}
}
// Use the table component edit form.
AccordionComponent.editForm = editForm.default;
// Register the component to the Formio.Components registry.
Components.addComponent('accordion', AccordionComponent);
<div class="accordion" id="formioAccordionPreview" *ngIf="components">
<div class="card" *ngFor="let component of components; first as isFirst">
<div class="card-header" id="heading-{{component.key}}">
<h2 class="mb-0">
<button type="button" class="btn btn-link" data-toggle="collapse" data-target="#collapse-{{component.key}}">{{component.label}}</button>
</h2>
</div>
<div id="collapse-{{component.key}}" class="collapse" [class.show]="isFirst" aria-labelledby="heading-{{component.key}}" data-parent="#formioAccordionPreview">
<div class="card-body">
<p>I should be able to 'Drag and Drop a form component' here.</p>
</div>
</div>
</div>
</div>
An Accordion is functionally identical to a tabs control, that is, headered content that facilitates switching and selection. The answer to constructing an accordion control was to extend the TabsComponent that's built into Form.io and override the createElement method that constructs the element via DOM manipulation. A couple of other overrides (schema and builderInfo) to provide metadata back to the FormBuilder and voila!
accordion.js
import TabsComponent from 'formiojs/components/tabs/Tabs';
import * as editForm from './Accordian.form';
export default class AccordionComponent extends TabsComponent {
/**
* Define what the default JSON schema for this component is. We will derive from the BaseComponent
* schema and provide our overrides to that.
* #return {*}
*/
static schema() {
return TabsComponent.schema({
type: 'accordion',
label: 'Sections',
input: false,
key: 'accordion',
persistent: false,
components: [{
label: 'Section 1',
key: 'section1',
type: 'tab',
components: []
}]
});
}
/**
* Register this component to the Form Builder by providing the "builderInfo" object.
*/
static get builderInfo() {
return {
title: 'Accordion',
group: 'custom',
icon: 'fa fa-tasks',
weight: 70,
schema: AccordionComponent.schema()
};
}
/**
* Tell the builder how to build this component using DOM manipulation.
*/
createElement() {
this.tabs = [];
this.tabLinks = [];
this.bodies = [];
this.accordion = this.ce('div', {
id: `accordion-${this.id}`
});
var _this = this;
this.component.components.forEach(function (tab, index) {
var isFirst = index === 0;
var tabLink = _this.ce('a', {
class: 'card-link',
data_toggle: 'collapse',
href: `#collapse-${tab.key}`
}, tab.label);
_this.addEventListener(tabLink, 'click', function (event) {
event.preventDefault();
_this.setTab(index);
});
var header = _this.ce('div', {
class: 'card-header'
}, [tabLink]);
var tabPanel = _this.ce('div', {
class: 'tab-pane',
role: 'tabpanel',
tabLink: tabLink
});
var tabContent = _this.ce('div', {
class: 'tab-content'
}, [tabPanel]);
var body = _this.ce('div', {
class: 'card-body',
id: tab.key
}, [tabContent]);
var content = _this.ce('div', {
id: `collapse-${tab.key}`,
class: 'collapse'.concat(isFirst ? ' show' : ''),
data_parent: `#accordion-${_this.id}`
}, [body]);
var card = _this.ce('div', {
class: 'card'
}, [header, body]);
_this.tabLinks.push(header);
_this.tabs.push(tabPanel);
_this.bodies.push(body);
_this.accordion.appendChild(card);
});
if (this.element) {
this.appendChild(this.element, [this.accordion]);
this.element.className = this.className;
return this.element;
}
this.element = this.ce('div', {
id: this.id,
class: this.className
}, [this.accordion]);
this.element.component = this;
return this.element;
}
setTab(index, state) {
super.setTab(index, state);
var _this = this;
if (this.bodies) {
this.bodies.forEach(function (body) {
body.style.display = 'none';
});
_this.bodies[index].style.display = 'block';
}
}
}
AccordionComponent.editForm = editForm.default;
The Accordion requires some different configuration in the edit form, so I also included a definition for the Display tab of the edit form:
Accordion.edit.display.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _default = [{
key: 'components',
type: 'datagrid',
input: true,
label: 'Sections',
weight: 50,
reorder: true,
components: [{
type: 'textfield',
input: true,
key: 'label',
label: 'Label'
}, {
type: 'textfield',
input: true,
key: 'key',
label: 'Key',
allowCalculateOverride: true,
calculateValue: {
_camelCase: [{
var: 'row.label'
}]
}
}]
}];
exports.default = _default;
And then the form definition override that references the custom Display Tab elements:
Accordion.form.js
"use strict";
require("core-js/modules/es.array.concat");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = _default;
var _NestedComponent = _interopRequireDefault(require("../../../../../../../../../node_modules/formiojs/components/nested/NestedComponent.form"));
var _AccordianEdit = _interopRequireDefault(require("./editForm/Accordian.edit.display"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _default() {
for (var _len = arguments.length, extend = new Array(_len), _key = 0; _key < _len; _key++) {
extend[_key] = arguments[_key];
}
return _NestedComponent.default.apply(void 0, [[{
key: 'display',
components: _AccordianEdit.default
}]].concat(extend));
}
The file structure for the Accordion component looks like this:
Then I just need to register the component in my Angular Project:
app.component.ts
import { Component } from '#angular/core';
import { Formio } from 'formiojs';
import AccordionComponent from './modules/utility/form-shell/cap-forms/cap-form-designer/components/accordian/accordian';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'app';
constructor() {
Formio.registerComponent('accordion', AccordionComponent);
}
}
Related
I am trying to develop a simple plugin for CKEditor 5. My ultimate goal is to have the plugin emit the following HTML:
<div class="icms_note">
<div class="span-6 module-note">
<p>User can edit here</p>
</div>
<div class="clear"></div>
</div>
I was having issues with the user editable area being in the wrong spot so I reduced the output to just this:
<div class="icms_note">
<p>User can edit here</p>
</div>
and the data model looks like this:
<icms_note>
<paragraph>User can edit here</paragraph>
</icms_note>
So I can get the data model created correctly and the HTML ends up in the editor the way I expect but I can't edit the text in the paragraph. I click on the text and the cursor just jumps out. I've tried looking at other examples and the tutorials but I can't seem to get it to work right. My plugin code is below. Any help would be appreciated.
note.js
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import NoteEditing from "./noteediting";
import NoteUI from "./noteui";
export default class Note extends Plugin {
static get requires() {
return [ NoteEditing, NoteUI ];
}
static get pluginName() {
return "IcmsNote";
}
}
noteui.js
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import { ButtonView } from "#ckeditor/ckeditor5-ui";
export default class NoteUI extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add( "icms_note", (locale) => {
const button = new ButtonView(locale);
button.set({
label: "Note",
withText: true,
tooltip: true
});
button.on( "execute", () => {
editor.execute("insertNote");
});
return button;
} );
}
}
noteediting.js
import Position from "#ckeditor/ckeditor5-engine/src/view/position";
import NoteCommand from "./notecommand";
export default class NoteEditing extends Plugin {
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add("insertNote", new NoteCommand(this.editor));
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register( "icms_note", {
inheritAllFrom: "$text",
allowIn: [ "$root", "$container" ],
isInline: true
});
}
_defineConverters() {
const conversion = this.editor.conversion;
conversion.for( "downcast" ).elementToElement({
model: "icms_note",
view: ( modelElementValue, conversionApi ) => {
const { writer } = conversionApi;
const outerDivElement = writer.createEditableElement("div", {class: "icms_note"});
return outerDivElement;
}
});//<div><div class=\"span-6 module-note\"><p>Enter note</p></div><div class=\"clear\"></div></div>
conversion.for( "upcast" ).elementToElement({
view: {
name: "div",
attributes: {
classes: [ "icms_note" ]
}
},
model: {
key: "icms_note",
value: viewElement => {
const val = viewElement.getChildren()[0].getChildren()[0].data;
return val;
}
}
});
}
}
notecommand.js
import Command from "#ckeditor/ckeditor5-core/src/command";
export default class NoteCommand extends Command {
constructor(editor) {
super(editor);
}
execute() {
console.log("NoteCommand#execute");
const model = this.editor.model;
const selection = model.document.selection;
model.change( modelWriter => {
let position = selection.getFirstPosition();
const icmsNote = modelWriter.createElement("icms_note");
const paragraph = modelWriter.createElement("paragraph");
modelWriter.insert(paragraph, icmsNote);
modelWriter.insertText("User can edit here", paragraph);
let positionElementName = position.parent.name;
while (positionElementName != "$root" && positionElementName != "$container") {
position = model.createPositionAfter(position.parent);
positionElementName = position.parent.name;
}
model.insertContent(icmsNote, position, null, {
setSelection: "after"
});
});
}
}
I have a modal component and I'm writing the story for it. It looks something like this:
import { Story, Meta } from '#storybook/html';
export default {
title: 'Components/Modal',
argTypes: {
open: {
name: 'Opened',
control: 'boolean'
},
},
args: {
open: false,
}
} as Meta;
const Template: Story = (args) => {
return `
<my-modal open="${args.open}">
Some example content inside the modal
</my-modal>
`;
};
export const Modal: Story = Template.bind({});
I have the arg open on the controls and I can change its value to true and the modal shows. But I would like the story to have a button and when it's clicked, the modal shows.
I can't find a way to do this in the current version of Storybook for web components.
I've seen there are some hooks available for React (import { useArgs } from '#storybook/api';) that allows you to change the arguments value dynamically but I can't see how to do this for web components?
Any helps will be highly appreciated.
Just add that button to the template:
import { Story, Meta } from '#storybook/html';
export default {
title: 'Components/Modal',
argTypes: {
open: {
name: 'Opened',
control: 'boolean'
},
},
args: {
open: false,
}
} as Meta;
const Template: Story = (args) => {
return `
<button
type="button"
onclick="this.nextElementSibling.open = !this.nextElementSibling.open">
Toggle Modal
</button>
<my-modal .open=${args.open}>
Some example content inside the modal
</my-modal>
`;
};
export const Modal: Story = Template.bind({});
Also, for boolean attributes - if implemented properly -
you should work with the property (prefix it in the template with a .) rather than the attribute.
Doing that with all native code isn't rocket science...
<my-dialog id="DIALOG" open>
Hello *Native* Web Components world!
</my-dialog>
<button onclick="DIALOG.open()">OPEN</button>
<script>
customElements.define("my-dialog", class extends HTMLElement {
static get observedAttributes() {
return ["open"];
}
constructor() {
super() // sets and returns 'this'
.attachShadow({mode:"open"}) // sets and return this.shadowRoot
.innerHTML = `<dialog><slot></slot><button>Close</button></dialog>`;
this.dialog = this.shadowRoot.querySelector("dialog");
}
connectedCallback() {
this.onclick = () => this.close(); // or attach to button
}
attributeChangedCallback(name,oldValue,newValue) {
this.open();
}
open() {
this.dialog.showModal(); // or .show()
}
close() {
this.dialog.close();
}
});
</script>
The method this.fillForm() of my Vue component C (EditComment) is called twice, but I'm having trouble understanding why. I tried using uuid, but don't know how it helps knowing that beforeCreate is called twice.
There are 3 components. Here are the relevant parts:
Component A:
showCommentDialog: function(recordNumber) {
this.$modal.show(
ShowComment,
{
commentRecId: recordNumber
},
{
draggable: true,
width: 400,
height: 250
},
{
closed: function(event) {}
}
);
Component B:
<EditComment v-bind:comment-rec-id="commentRecId" v-if="showEdit"></EditComment>
</div>
</template>
<script>
import * as $ from "jquery";
import EditComment from "./EditComment.vue";
export default {
props: ["commentRecId"],
data: function() {
with this function
editItem: function(){
this.showEdit = true;
console.log("editItem function() called!");
var playerID = this.$store.state.selectedPlayer.ID;
this.$modal.show(
EditComment,
{
text: playerID
},
{
draggable: true,
width: 400,
height: 400
})
}
Component C:
<script>
import * as $ from "jquery";
import DatePicker from "vue2-datepicker";
let uuid = 0;
export default {
props: ["text", "commentRecId"],
beforeCreate() {
this.uuid = uuid.toString();
uuid += 1;
console.log("beforeCreate() uuid: " + this.uuid);
},
components: { DatePicker },
data: function() {
return {
commentData: {
comment: "",
customDate: ""
},
selectedCategory: "",
lang: {
default: "en"
},
}
},
mounted: function() {
// console.log("this._uid: " + this._uid);
this.fillForm();
},
methods: {
fillForm: function(){
Any help is appreciated.
If I understand correctly your problem, you fired component C with this section of editItem method:
this.$modal.show(
EditComment,
{
text: playerID
},
{
draggable: true,
width: 400,
height: 400
})
if I'm right, you have a mistake in your method:
when you use v-if, vue fires your component and it resets all values which you passed it before, like props, data values (except your uuid because it's not a data property)
so in your method you fire your component twice with :
this.showEdit = true;
anyway...for solution, please try this way:
first, use "v-show" instead of "v-if"
then show your component by this.$modal.show()
I hope can help
I'm trying to work with a checkbox tree component like this: https://www.npmjs.com/package/react-checkbox-tree, except I'm storing the items that I have selected in Redux. Moreover, the only items that I'm actually storing are the leaf nodes in the tree. So for example, I'd have the full options data which would be used to render the tree:
const fam = {
cuz2: {
name: 'cuz2',
children: {
cuzKid2: {
name: 'cuzKid2',
children: {
}
}
}
},
grandpa: {
name: 'grandpa',
children: {
dad: {
name: 'dad',
children: {
me: {
name: 'me',
children: {}
},
sis: {
name: 'sis',
children: {}
}
}
},
aunt: {
name: 'aunt',
children: {
cuz: {
name: 'cuz',
children: {
name: 'cuzkid',
children: {}
}
}
}
}
}
}
and a separate object that stores the items selected. The following would be the only items that would appear if every checkbox was checked:
const selected = {
cuz2: true,
me: true,
sis: true,
cuz: true
}
I seem to be struggling with this method for having the UI determine which boxes to have fully, partially, or un-checked based on the selected object. I was wondering if anyone can recommend another strategy of accomplishing this.
So I have used react-checkbox-tree but I have customised a bit the icons in order to use another icons library.
Check my example on sandbox:
The library provides a basic example of how to render a tree with selected and/or expanded nodes.
All you need to do is:
set up the nodes with a unique 'value'
Choose which items should be selected (it may comes from Redux)
pass nodes & checked list to the CheckBox constructor
also be sure that when user select/unselect, you update the UI properly using the state
Your code should look similar to this:
import React from 'react';
import CheckboxTree from 'react-checkbox-tree';
const nodes = [{
value: '/cuz2',
label: 'cuz2',
children: [],
},
// other nodes
];
class BasicExample extends React.Component {
state = {
checked: [
'/cuz2'
],
expanded: [
'/cuz2',
],
};
constructor(props) {
super(props);
this.onCheck = this.onCheck.bind(this);
this.onExpand = this.onExpand.bind(this);
}
onCheck(checked) {
this.setState({
checked
});
}
onExpand(expanded) {
this.setState({
expanded
});
}
render() {
const {
checked,
expanded
} = this.state;
return (<
CheckboxTree checked={
checked
}
expanded={
expanded
}
nodes={
nodes
}
onCheck={
this.onCheck
}
onExpand={
this.onExpand
}
/>
);
}
}
export default BasicExample;
I am working on an app for my portfolio with Angular 2 where I have filtering functionality via checkboxes. These checkboxes apply name filtering to the lists of technologies I've used for each project I've worked on. In the app I also have a like functionality that allows you to indicate that you like a project. For this app I need to keep track of the number of likes that each project receives. In my code I would like to add a service to my project component that allows me to not loose the number of likes and filtered projects as I switch views with my router. In my code I have an array of Project objects, each take the following form- new Project( "Web Design Business Website", ["Wordpress", "Bootstrap","PHP","HTML5","CSS3"], 0 ) I am able to get the filtering and favoriting functionality to work if I don't use a service, but I need to use one for the aformentioned reasons and when I attempt to import in my service and use it, I am unable to see my template html on the screen and I get the following errors-
EXCEPTION: Error: Uncaught (in promise): EXCEPTION: Error in :0:0
ORIGINAL EXCEPTION: TypeError: Cannot read property 'filter' of undefined
ERROR CONTEXT:
[object Object]
My Project component code is as follows:
import { ROUTER_DIRECTIVES, Routes } from '#angular/router';
import { Component, OnInit } from '#angular/core';
import { Project } from './project';
import { ProjectService } from './project.service';
#Component({
selector: 'my-app',
host: {class: 'dashboard'},
templateUrl: 'app/app.component.html',
providers: [ProjectService]
})
export class ProjectsComponent implements OnInit {
allProjects: Project[];
constructor(private projectService: ProjectService) {
this.updateSelectedList();
}
getProjects(){
this.allProjects = this.projectService.getProjects();
}
ngOnInit(){
this.getProjects();
}
title: string = 'Filter Projects by Technology';
/* all possible tech */
technologyList : Array<any> = [
{
name:"Javascript",
checked: true
}, {
name: "PHP",
checked: true
}, {
name: "HTML5",
checked: true
}, {
name: "CSS3",
checked: true
}, {
name: "AngularJS",
checked: true
}, {
name: "BackboneJS",
checked: true
}, {
name: "KnockoutJS",
checked: true
}, {
name: "Bootstrap",
checked: true
}, {
name: "Wordpress",
checked: true
},
{
name: "Photoshop",
checked: true
}
];
/* projects that match the selected tech */
matchedProjects: Array<any> = []
/* The checked items in the list */
selectedTechnology: Array<string> = [];
favUp(project): boolean {
project.favUp();
return false;
}
onInteractionEvent(event: Event) {
var item = this.technologyList.find(
(val) => val.name === event.target.value
);
item.checked = !item.checked;
this.updateSelectedList();
}
updateSelectedList() {
let checkedNames =
this.technologyList.filter( (val) => val.checked === true).map(n => n.name);
this.matchedProjects = this.allProjects.filter(project => {
return this.containsAny(project.technologies, checkedNames)
});
}
containsAny(arr1, arr2) {
for(var i in arr1) {
if(arr2.indexOf( arr1[i] ) > -1){
return true;
}
}
return false;
};
}
My project class is located in another file that I import and is as follows:
export class Project {
name: string;
technologies: Array<any>;
favs: number;
constructor(name: string, technologies: Array<any>, favs: number) {
this.name = name;
this.technologies = technologies;
this.favs = favs || 0;
}
favUp(): void {
this.favs += 1;
}
}
My array of project objects is located in another file and is as follows:
import { Project } from './project';
/* all the projects worked on */
export var ALLPROJECTS: Project[] = [
new Project( "Web Design Business Website", ["Wordpress", "Bootstrap","PHP","HTML5","CSS3"], 0 ),
new Project( "Vocab Immersion Trainer App", ["AngularJS","Javascript","Bootstrap","HTML5","CSS3"], 0)
];
My project service is located in another file that I import and is as follows:
import { Injectable } from '#angular/core';
import { ALLPROJECTS } from './all-projects';
#Injectable()
export class ProjectService {
getProjects() {
return ALLPROJECTS;
}
}
My template html for the project component is as follows:
<p>
<a class="btn btn-large btn-primary" data-toggle="collapse" href="#collapseExample" aria-expanded="false" aria-controls="collapseExample">
{{title}}
</a>
</p>
<div class="collapse" id="collapseExample">
<div class="card card-block">
<label *ngFor="let item of technologyList">
<input type="checkbox"
value="{{item.name}}"
[checked]="item.checked"
(change)="onInteractionEvent($event)">
{{ item.name }}
</label>
</div>
</div>
<h2>Featured Projects</h2>
<div *ngFor="let project of matchedProjects" class="container">
<div class="row">
<div class="col-sm-4 col-sm-offset-1 card">
<img src="http://placehold.it/350x150" class="card-img-top img-fluid img-rounded center-block" data-src="..." alt="Card image cap">
<div class="card-block text-xs-center">
<h4 class="card-title">Project Name: {{project.name}} </h4>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content. This is alot of text. It adds length to the paragraph. It adds bulk. I had to do it. It was very necessary for this example</p>
<a class="btn btn-primary">See live site</a>
</div>
<p> {{ project.favs }} Likes <a href (click)="favUp(project)">Like</a></p>
</div>
<div class="col-sm-6 text-xs-center">
<h2 >Technology used</h2>
<p>{{project.technologies}}</p>
</div>
</div>
</div>
My previous project component code that works but doesn't retain data between views is as follows
import { ROUTER_DIRECTIVES, Routes } from '#angular/router';
import { Component } from '#angular/core';
/* import { Suggestion, SuggestionsComponent, SuggestionsView } from './suggestions.component'; */
export class Project {
name: string;
technologies: Array<any>;
favs: number;
constructor(name: string, technologies: Array<any>, favs: number) {
this.name = name;
this.technologies = technologies;
this.favs = favs || 0;
}
favUp(): void {
this.favs += 1;
}
}
#Component({
selector: 'my-app',
host: {class: 'dashboard'},
templateUrl: 'app/projects.component.html'
})
export class ProjectsComponent {
title: string = 'Filter Projects by Technology';
/* all possible tech */
technologyList : Array<any> = [
{
name:"Javascript",
checked: true
}, {
name: "PHP",
checked: true
}, {
name: "HTML5",
checked: true
}, {
name: "CSS3",
checked: true
}, {
name: "AngularJS",
checked: true
}, {
name: "BackboneJS",
checked: true
}, {
name: "KnockoutJS",
checked: true
}, {
name: "Bootstrap",
checked: true
}, {
name: "Wordpress",
checked: true
},
{
name: "Photoshop",
checked: true
}
];
/* all the projects worked on */
allProjects = [
new Project( "Web Design Business Website", ["Wordpress", "Bootstrap","PHP","HTML5","CSS3"], 0 ),
new Project( "Vocab Immersion Trainer App", ["AngularJS","Javascript","Bootstrap","HTML5","CSS3"], 0)
];
/* projects that match the selected tech */
matchedProjects: Array<any> = []
/* The checked items in the list */
selectedTechnology: Array<string> = [];
favUp(project): boolean {
project.favUp();
return false;
}
constructor() {
this.updateSelectedList();
}
onInteractionEvent(event: Event) {
var item = this.technologyList.find(
(val) => val.name === event.target.value
);
item.checked = !item.checked;
this.updateSelectedList();
}
updateSelectedList() {
let checkedNames =
this.technologyList.filter( (val) => val.checked === true).map(n => n.name);
this.matchedProjects = this.allProjects.filter(project => {
return this.containsAny(project.technologies, checkedNames)
});
}
containsAny(arr1, arr2) {
for(var i in arr1) {
if(arr2.indexOf( arr1[i] ) > -1){
return true;
}
}
return false;
};
}
Here is the github link to the working version of the project before the incorporation of the service: Portfolio Application link