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
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 want to render a component into a Angular dialog. This component is used on on a full page of my application and it has children.
When I add the selector into my modal, I've got an error : ExpressionChangedAfterItHasBeenCheckedError
This error happens on the page which loads the modal.
Is it possible to reuse a component into an Angular modal with his inputs and outputs ?
<!-- modal -->
<ng-container>
<div class="close-button-wrapper">
<button (click)="onCloseClick()" mat-icon-button>
<mat-icon>close</mat-icon>
</button>
</div>
<div class="dialog-content">
<picture-container></picture-container> <!-- not working ExpressionChangedAfterItHasBeenCheckedError -->
</div>
<div class="dialog-footer">
<raised-button-round class="action-button">Cancel</raised-button-round>
<raised-button-round icon="save" color="primary" class="action-button">OK</raised-button-round>
</div>
</ng-container>
<!-- component I want to reuse -->
#Component({
selector: 'picture-container',
template: `
<ng-container *ngIf="data$ | async as data">
<picture-presenter
[images]="data.pictures"
[tagImages]="data.tagImages"
[totalCountItems]="data.totalCountItems"
[tags]="data.tags"
[pageSize]="20"
[isSelectablePicture]="data.isSelectablePicture"
(displayableContent)="onChangeDisplay($event)"
(searchTagSelect)="onSearchTagSelect($event)"
(importPicture)="onImportPicture()"
(pictureAction)="onPictureAction($event)"
(changePage)="pageChange$.next($event)"
></picture-presenter>
</ng-container>
<upload-widget #uploadWidget (upload)="onUpload($event)"></upload-widget>
`,
styles: [``],
})
export class PictureContainerComponent implements OnDestroy, OnInit {
#ViewChild('uploadWidget') uploadWidget?: UploadWidgetComponent
data$: Observable<
Maybe<{
pictures: Maybe<PictureFragment>[]
tagImages: TagImage[]
tags: Maybe<{ name: string; count: number }>[]
totalCountItems: number,
isSelectablePicture: boolean
}>
> = of(null)
destroyed$ = new Subject<boolean>()
pageChange$ = new BehaviorSubject<PaginatorEvent>({})
personalImages$ = new BehaviorSubject<boolean>(true)
searchTags$ = new BehaviorSubject<string[]>([])
constructor(public store: Store) { }
ngOnInit(): void {
combineLatest([
this.store.select(selectStore),
this.store.select(selectGalleries),
this.searchTags$,
this.pageChange$,
this.personalImages$,
this.store.select(selectPictureUpdated),
])
.pipe(takeUntil(this.destroyed$))
.subscribe(
([store, galleries, tags, direction, isPersonal, picUpdated]) => {
if (!galleries) return
// TODO - High - Get right gallery
const galleriesId = galleries.map((gal) => gal?.id || '')
const filters = {
galleries: !isPersonal ? galleriesId : [],
tags: !isPersonal ? tags : [],
owner: store?.id,
}
this.store.dispatch(
loadPictures({
filters,
paginate: {
count: 15,
},
paginatorEvent: direction,
noCache: picUpdated != null ? true : false,
})
)
this.store.dispatch(
loadTags({
search: {
galleries: galleriesId,
owner: store?.id,
},
})
)
}
)
this.data$ = combineLatest([
this.store.select(selectPicturesState),
of(TAG_IMAGES),
this.store.select(selectPagination),
this.store.select(selectTags),
this.store.select(selectIsSelectablePicture),
]).pipe(
takeUntil(this.destroyed$),
map(([picturesState, tagImages, pagination, tags, isSelectablePicture]) => {
return {
pictures: picturesState?.pictures || [],
tagImages,
totalCountItems: pagination?.totalCount || 0,
tags,
isSelectablePicture
}
})
)
}
ngOnDestroy(): void {
this.destroyed$.next(true)
this.destroyed$.complete()
}
onSearchTagSelect(tags: string[]) {
this.pageChange$.next({})
this.searchTags$.next(tags)
}
onImportPicture() {
if (this.uploadWidget) {
this.uploadWidget?.openDialog({ pictureType: 'food' })
}
}
onPictureAction($event: {
picture: Maybe<PictureFragment>
action: 'edit' | 'delete'
}) {
if (!$event.picture) return
const picture = parsePictureFragmentToPictureInput($event.picture)
if (!picture) return
if ($event.action === 'edit') {
this.store.dispatch(
setUpdatePicture({
picture,
})
)
} else {
this.store.dispatch(setDeletePicture({ picture }))
}
}
onUpload($event: any) {
this.store.select(selectStore).subscribe((store) => {
this.store.dispatch(
setUploadPicture({
picture: { ...$event, owner: store?.id },
})
)
})
}
onChangeDisplay($event: 'import' | 'suggestions' | 'search') {
this.personalImages$.next($event !== 'search')
}
Thank you for your help
I'm building an application to power the backend of a website for a restaurant chain. Users will need to edit page content and images. The site is fairly complex and there are lots of nested pages and sections within those pages. Rather than hardcode templates to edit each page and section, I'm trying to make a standard template that can edit all pages based on data from the route.
I'm getting stuck on the v-model for my text input.
Here's my router code:
{
path: '/dashboard/:id/sections/:section',
name: 'section',
component: () => import('../views/Dashboard/Restaurants/Restaurant/Sections/Section.vue'),
meta: {
requiresAuth: true
},
},
Then, in my Section.vue, here is my input with the v-model. In this case, I'm trying to edit the Welcome section of a restaurant. If I was building just a page to edit the Welcome text, it would work no problem.:
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
This issue is that I need to reference the "welcome" part of the v-model dynamically, because I've got about 40 Sections to deal with.
I can reference the Section to edit with this.$route.params.section. It would be great if I could use v-model="restInfo. + section", but that doesn't work.
Is there a way to update v-model based on the route parameters?
Thanks!
Update...
Here is my entire Section.vue
<template>
<div>
<Breadcrumbs :items="crumbs" />
<div v-if="restInfo">
<h3>Update {{section}}</h3>
<div class="flex flex-wrap">
<div class="form__content">
<form #submit.prevent>
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
<div class="flex">
<button class="btn btn__primary mb-3" #click="editText()">
Update
<transition name="fade">
<span class="ml-2" v-if="performingRequest">
<i class="fa fa-spinner fa-spin"></i>
</span>
</transition>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { VueEditor } from "vue2-editor"
import Loader from '#/components/Loader.vue'
import Breadcrumbs from '#/components/Breadcrumbs.vue'
export default {
data() {
return {
performingRequest: false,
}
},
created () {
this.$store.dispatch("getRestFromId", this.$route.params.id);
},
computed: {
...mapState(['currentUser', 'restInfo']),
section() {
return this.$route.params.section
},
identifier() {
return this.restInfo.id
},
model() {
return this.restInfo.id + `.` + this.section
},
crumbs () {
if (this.restInfo) {
let rest = this.restInfo
let crumbsArray = []
let step1 = { title: "Dashboard", to: { name: "dashboard"}}
let step2 = { title: rest.name, to: { name: "resthome"}}
let step3 = { title: 'Page Sections', to: { name: 'restsections'}}
let step4 = { title: this.$route.params.section, to: false}
crumbsArray.push(step1)
crumbsArray.push(step2)
crumbsArray.push(step3)
crumbsArray.push(step4)
return crumbsArray
} else {
return []
}
},
},
methods: {
editText() {
this.performingRequest = true
this.$store.dispatch("updateRest", {
id: this.rest.id,
content: this.rest
});
setTimeout(() => {
this.performingRequest = false
}, 2000)
}
},
components: {
Loader,
VueEditor,
Breadcrumbs
},
beforeDestroy(){
this.performingRequest = false
delete this.performingRequest
}
}
</script>
Try to use the brackets accessor [] instead of . :
<vue-editor v-model="restInfo[section]"
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);
}
}
I am using angular 6 application and i am trying to make a multiple select using input box without any third party plugin, jquery, datalist, select box and it is pure input box, typescript based.
HTML:
<div class="autocomplete">
<input name="suggestion" type="text" placeholder="User" (click)="suggest()" [formControl]="typeahead">
<div class="autocomplete-items" *ngIf="show">
<div *ngFor="let s of suggestions" (click)="selectSuggestion(s)">{{ s }}</div>
</div>
</div>
TS:
import { Component } from '#angular/core';
import { FormControl } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
suggestions: string [] = [];
suggestion: string;
show: boolean;
typeahead: FormControl = new FormControl();
fieldHistory: string [] = [];
suggest() {
this.suggestions = this.users;
this.show = true;
}
selectSuggestion(s) {
this.suggestion = "";
this.fieldHistory.push(s)
for (let i = 0; i < this.fieldHistory.length; i++)
this.suggestion = this.suggestion + " " + this.fieldHistory[i];
this.typeahead.patchValue(this.suggestion);
this.show = false;
}
users = ['First User', 'Second User', 'Third User', 'Fourth User'];
}
Here i need to delete the selected values like the angular material chips, User is able to select multiple values but he also can delete the wrongly selected values.
How can i make a delete option for each individual items to delete the wrongly selected values inside the input box?
Stackblitz link with multi select option https://stackblitz.com/edit/angular-dndhgv
Any edit in the above link to make the multi select with delete option would also be much more appreciable..
Please try this.
component.ts
import { Component } from '#angular/core';
import { FormControl } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
suggestions: string [] = [];
suggestion: string = '';
show: boolean;
typeahead: FormControl = new FormControl();
fieldHistory: string [] = [];
suggest() {
this.suggestions = this.users;
this.show = true;
}
selectSuggestion(s,status) {
this.suggestion = '';
if(status){
this.fieldHistory.push(s);
this.typeahead.patchValue(this.fieldHistory);
}else{
this.fieldHistory.forEach((element,index) => {
if(element == s){
this.fieldHistory.splice(index,1);
}
});
this.typeahead.patchValue(this.fieldHistory);
}
}
users = ['First User', 'Second User', 'Third User', 'Fourth User'];
}
Html
<div class="autocomplete">
<input name="suggestion" type="text" placeholder="User" (click)="suggest()" [formControl]="typeahead">
<div class="autocomplete-items" *ngFor="let s of suggestions">
<input type="checkbox" name='{{s}}' (click)="selectSuggestion(s,$event.target.checked)" />{{s}}
</div>
</div>
I'm not an Angular developer, but i tried to do solution.
Chosen phrases from suggested are storing in "chosen" variable. You can type something and divide it by "," to store it in "chosen" like in angular material chips.
Stackblitz
Maybe you should use a selected field for your users object, as following :
users = [
{
name: 'First User',
selected: false
},
{
name: 'Second User',
selected: false
},
{
name: 'Third User',
selected: false
},
{
name: 'Fourth User',
selected: false
}
]
The new html would be:
<div class="autocomplete">
<div (click)="showChoices()" style="border: solid 1px; display: flex">
<span *ngIf="!selectedUsers.length">Users</span>
<div *ngFor="let user of selectedUsers">
{{user.name}} <a style="cursor: pointer" (click)="unselectUser(user)">x</a>
</div>
</div>
<div class="autocomplete-items" *ngIf="show">
<div *ngFor="let user of users" [ngClass]="user.selected ? 'selected-suggestion' : ''" (click)="selectUser(user)">{{user.name}}</div>
</div>
</div>
And the .ts :
selectedUsers: { name: string, selected: boolean }[] = [];
show: boolean = false;
selectUser(user: { name: string, selected: boolean }) {
if (!user.selected) {
user.selected = true;
}
this.selectedUsers = this.users.filter((u) => u.selected);
console.log(this.selectedUsers)
}
unselectUser(user: { name: string, selected: boolean }) {
if (user.selected) {
user.selected = false;
}
this.selectedUsers = this.users.filter((u) => u.selected);
console.log(this.selectedUsers)
}
showChoices() {
if (this.selectedUsers.length) {
return;
}
this.show = !this.show;
}
Here is the working stackblitz.