I'm trying to create a new control for FormBuilder, it's basically a radio-group control (I mean it has the same config of a radio-group control) but I need to define a custom build() and onRender() method.
I read docs a lot of times but can't get it to work, here is a simple example of what I'm trying to do.
if (!window.fbControls) window.fbControls = new Array();
window.fbControls.push(function (controlClass) {
class controlMultipleObjects extends controlClass {
static get definition() {
return {
icon: '\uD83D\uDD89',
i18n: {
default: 'Control Multiple Items'
}
};
}
configure() {
// this.js = '//cdn.jsdelivr.net/npm/signature_pad#2.3.2/dist/signature_pad.min.js';
}
/**
* build a text DOM element, supporting other jquery text form-control's
* #return DOM Element to be injected into the form.
*/
build() {
this.dom = this.markup('div', null, {class:'multipleObjectsController', id: this.config.name});
return this.dom;
}
onRender() {
}
}
// register this control for the following types & text subtypes
controlClass.register('multipleObjects', controlMultipleObjects);
return controlMultipleObjects;
});
This basically works, the new control 'multipleObjects' is shown in form builder, but when opening the config for the control it only shows the 'Value' item. I need to show multiple values just like the radio-group, select or select-group controls.
Any ideas?
Thanks!
Take a look at https://formbuilder.online/docs/formBuilder/options/typeUserAttrs
you'll need to pass options in your formBuilder like that:
const options = {
typeUserAttrs: {
multipleObjects: {
options: {
label: 'Class',
multiple: true,
options: {
'red form-control': 'Red',
'green form-control': 'Green',
'blue form-control': 'Blue'
},
},
},
},
}
$("#fb-editor").formBuilder(options)
Related
I am using angular-silkgrid with angular 7. I am using inline editing feature. I am using single select editor for inline edit. Currently I want to achieve this functionality:
As soon as user click on the editable field , the select drop down will be visible.On select any option from select dropdown the inline mode should exist and column value should be updated.
currently I need to click on other field to exit from inline mode(I want to achieve this on select option select)
editor: {
// display checkmark icon when True
enableRenderHtml: true,
// tslint:disable-next-line:max-line-length
collection: [{
value: 1,
label: 'Sucessful'
}, {
value: 2,
label: 'Unsucessful'
}],
model: Editors.singleSelect, // CustomSelectEditor
elementOptions: {
autoAdjustDropPosition: false,
onClick: (event, rr) => {
// here i want to write some code which can trigger to grid to start update
}
}
}
Thanks All for the answers. I have solved my issue as below:
editor: {
enableRenderHtml: true,
collection: [{ value: CCLStaus.Sucessfull, label: 'Sucessful' }, { value: CCLStaus.UnSucessfull, label: 'Unsucessful' }],
model: Editors.singleSelect,// CustomSelectEditor
elementOptions: {
onClick: (event) => {
const updateItem = this.angularSilkGrid.gridService.getDataItemByRowIndex(this.rowInEditMode);
updateItem.status = +event.value;
this.angularSilkGrid.gridService.updateItem(updateItem, { highlightRow: false });
this.angularSilkGrid.gridService.renderGrid();
}
}
}
Generally,
grid.getEditorLock().commitCurrentEdit()
will commit and close the editor.
Also, any of
grid.navigateRight()
grid.navigateLeft()
grid.navigateDown()
grid.navigateUp()
grid.navigateNext()
grid.navigatePrev()
will commit and exit gracefully. In the select2 editor, you'll notice:
this.init = function () {
...
// Set focus back to select2 element on closing.
$input.on('select2:close', function (e) {
if ((e.params.originalSelect2Event && e.params.originalSelect2Event.data)
|| e.params.key === 9) {
// item was selected with ENTER or no selection with TAB
args.grid.navigateNext();
} else {
// closed with no selection
setTimeout(function () {
$input.select2('focus');
}, 100);
}
});
};
this.destroy = function () {
$input.select2('destroy');
$input.remove();
};
, noting that args.grid.navigateNext() will commit and close the editor, including calling the destroy() method at the appropriate time.
From the Angular-Slickgrid Editor Example there's a checkbox in the example to auto commit and that is a setting to you need to enable in your Grid Options
this.gridOptions = {
autoEdit: true,
autoCommitEdit: true,
}
The lib will internally call grid.getEditorLock().commitCurrentEdit() like Ben wrote in his answer, in Angular-Slickgrid you can just set the autoCommitEdit flag that I added.
In CKEditor5 I am creating a plugin to insert a span element to show a tooltip. The idea is to show a tooltip with a (foot)note inside of it while the element itself will display an incremental number. In CKEditor4 I made something like this with:
CKEDITOR.dialog.add( 'footnoteDialog', function( editor ) {
return {
title: 'Footnote Properties',
minWidth: 400,
minHeight: 100,
contents: [
{
id: 'tab-basic',
label: 'Basic Settings',
elements: [
{
type: 'text',
id: 'content',
label: 'Content of footnote',
validate: CKEDITOR.dialog.validate.notEmpty( "Footnote field cannot be empty." )
}
]
}
],
onOk: function() {
var dialog = this;
var footnote = editor.document.createElement( 'span' );
footnote.setAttribute('class', 'footnote');
footnote.setAttribute('data-toggle', 'tooltip');
footnote.setAttribute( 'title', dialog.getValueOf( 'tab-basic', 'content' ) );
footnote.setText('[FN]');
editor.insertElement( footnote );
}
};
});
[FN] would be transformed in an incremental number.
Now I try to make this plugin with in CKEditor5, but with no success. There are two issues I run in to. Fist, I can't manage to insert the element inside the text. Second, when I want to use the attribute data-toggle this doesn't work because of the - syntax. This is my current code:
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import pilcrowIcon from '#ckeditor/ckeditor5-core/theme/icons/pilcrow.svg';
import ButtonView from '#ckeditor/ckeditor5-ui/src/button/buttonview';
export default class Footnote extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add( 'footnote', locale => {
const view = new ButtonView( locale );
view.set( {
label: 'Insert footnote',
icon: pilcrowIcon,
tooltip: true
} );
view.on( 'execute', () => {
const source = prompt( 'Footnote' );
editor.model.schema.register( 'span', { allowAttributes: ['class', 'data-toggle', 'title'] } );
editor.model.change( writer => {
const footnoteElement = writer.createElement( 'span', {
class: 'footnote',
// data-toggle: 'tooltip',
title: source
});
editor.model.insertContent( footnoteElement, editor.model.document.selection );
} );
} );
return view;
} );
}
}
How can I make sure my span element is inserted and also contains data-toggle="tooltip"?
For anyone who comes across this, there is a good description of how to set up inline elements in the model and view and then map between them here - How to add "target" attribute to `a` tag in ckeditor5?
Based on my experience, you will also need to set up Javascript code for a command that is run when a button is pressed. The command will insert the new information into the model, then this mapping code will convert it to the view (HTML) for display.
document_table_Settings : function ()
{
return{
rowsPerPage: 5,
showNavigation: 'auto',
showColumnToggles: false,
fields: [
{key: 'para',label: 'Para',sortable: false},
{key: 'desc', label: 'Description',sortable: false},
{
key: 'rowId', label: 'Delete',sortable: false, fn: function (rowId, object) {
var html = "<button name='Del' id=" + rowId + " class='btn btn-danger'>Delete</button>"
return new Spacebars.SafeString(html);
}
},
{
key: 'rowId', label: 'Edit',sortable: false, fn: function (rowId, object) {
var html = "<button name='edit' id=" + rowId + " class='btn btn-warning'>Edit</button>"
return new Spacebars.SafeString(html);
}
}
]
};
}
I want to show description entries having show more and show less feature .As the description is long enough. so after 100 character it shows button to toggle.
If I understand you correctly, you are trying to only show the first 100 characters of the 'Description' column in the Reactive Table and then add some mechanism so that the user can click or rollover to see the entire 'Description' text.
There are a few ways to achieve this and I have provided two options below (in order of simplicity).
For a low tech rollover option, truncate the text to only show the first 100 characters, add an ellipsis (...) to the end of your text, then use the title property in a span element to show the full text on rollover.
First you will need to define a 'truncate' Template helper (I would make this a global helper so that you can use anywhere in your app).
Template.registerHelper('truncate', function(strValue, length) {
var len = DEFAULT_TRUNCATE_LENGTH;
var truncatedString = strValue;
if (length && length instanceof Number) {
len = length;
}
if (strValue.length > len) {
truncatedString = strValue.substr(1, len) + "...";
}
return truncatedString;
});
Then create a new Template for the column.
<template name="field_description">
<span title="{{data.description}}">{{truncate data.description}}</span>
</template>
And finally, change your Reactive Table configuration to use a Template.
fields: [
...,
{ key: 'desc', label: 'Description', tmpl: Template.field_description }
...,
];
For a slightly more complicated option, you can take a similar approach but add a clickable link that would show more or less detail. To get it to work you have to define a few Reactive Vars, define an event handler, and change your 'Description' Template accordingly. Here is a rough solution that should work.
Change your template like so.
<template name="field_description">
<span>{{truncatedDescription}}
{{#if showLink}}
{{linkState}}
{{/if}}
</span>
</template>
Then add the necessary logic to your field_description template (including an event handler).
import { Template } from 'meteor/templating';
import './field-description.html';
Template.field_descirption.onCreated(function() {
const MAX_LENGTH = 100;
this.description = new ReactiveVar(Template.currentData().description);
this.showMore = new ReactiveVar(true);
if (this.description.get().length > MAX_LENGTH) {
this.description.set(Template.currentData().description.substr(1, MAX_LENGTH));
}
this.showLink = () => {
return Template.currentData().description.length > MAX_LENGTH;
};
this.toggleTruncate = () => {
if (this.showMore.get()) {
this.description.set(Template.currentData().description);
this.showMore.set(false);
} else {
this.description.set(Template.currentData().description.substr(1, MAX_LENGTH));
this.showMore.set(true);
}
};
});
Template.field_descirption.helpers({
truncatedDescription: function() {
return Template.instance().description.get();
},
showLink: function() {
return Template.instance().showLink();
},
linkState: function() {
if (Template.instance().showMore.get()) {
return 'show more';
} else {
return 'show less';
}
};
});
Template.field_descirption.events({
'click .js-more-less'(event, instance) {
instance.toggleTruncate();
},
});
Lastly, make sure your Reactive Table config is still setup to use a Template for the field.
fields: [
...,
{ key: 'desc', label: 'Description', tmpl: Template.field_description }
...,
];
Note that the second option makes use of Meteor's Reactivity to solve the problem. Let me know if you need additional explanation on how the 2nd solution works.
That should do it!
I want to do something like this jsfiddle example, I need to put some custom attributes on left panel properties. Below I tried to make similarly but I can't drag the field
YUI().use('aui-form-builder',function (Y) {
Y.MyFormCustom = Y.Component.create({
NAME: 'form-node',
ATTRS: {
type: {
value: 'custom'
},
customAttr: {
validator: Y.Lang.isString,
value: 'A Custom default'
}
},
EXTENDS: Y.FormBuilderFieldBase,
prototype: {
getPropertyModel: function () {
var instance = this;
var model = Y.FormBuilderFieldBase.superclass.getPropertyModel.apply(instance, arguments);
model.push({
attributeName: 'customAttr',
name: 'Custom Attribute'
});
return model;
}
}
});
Y.FormBuilder.types['custom'] = Y.MyFormCustom;
var availableFields = [
{
iconClass: 'form-builder-field-icon-button',
label: 'Button',
type: 'custom'
}
];
myform= new Y.FormBuilder({
availableFields: availableFields,
boundingBox: '#myHolder'
}).render();
I don't know why the form is not appearing. Any help will be appreciated.
Your example has been very helpful to me because I also needed to extend the Form Builder fields.
The fix to the above is simple.
Replace the line:
Y.FormBuilder.types['custom'] = Y.MyFormCustom;
by
Y.FormBuilderField.types['custom'] = Y.MyFormCustom;
This solution is inspired from the source code found in the Alloy UI API.
See link:
AlloyUI Form Builder
Cheers
Using the example from https://help.rallydev.com/apps/2.0rc2/doc/#!/guide/timebox_filtering for a timebox required app, how do I convert the cardBoard view into a grid?
This is the base code:
Ext.define('Rally.guide.ReleaseFilteredBoard', {
extend: 'Rally.app.TimeboxScopedApp',
scopeType: 'release',
onScopeChange: function(scope) {
if(!this.board) {
this.board = this.add({
xtype: 'rallycardboard',
storeConfig: {
filters: [scope.getQueryFilter()]
}
});
} else {
this.board.refresh({
storeConfig: {
filters: [scope.getQueryFilter()]
}
});
}
}
});
It seems that I can simply change the xtype to 'rallygrid' and based on docs it should work but it seems to need a model defined as well - how do I get the model details out of the timebox scope to feed into the grid?
You may wish to check out the example code for Rally.ui.grid.Grid.
Here's a quick sample of how one might apply the Timebox filter to a grid:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
grid: null,
launch: function() {
var filters = [];
var timeboxScope = this.getContext().getTimeboxScope();
if(timeboxScope) {
filters.push(timeboxScope.getQueryFilter());
}
this.getFilteredStoryModel(filters);
},
onTimeboxScopeChange: function(newTimeboxScope) {
var newFilters = [];
var updatedTimeboxScope = this.getContext().getTimeboxScope();
if (this.grid) {
this.grid.destroy();
}
if (updatedTimeboxScope) {
newFilters.push(newTimeboxScope.getQueryFilter());
}
this.getFilteredStoryModel(newFilters);
},
getFilteredStoryModel: function(queryFilters) {
Rally.data.ModelFactory.getModel({
type: 'UserStory',
success: function(model) {
this.grid = this.add({
xtype: 'rallygrid',
model: model,
columnCfgs: [
'FormattedID',
'Name',
'Owner',
'Iteration'
],
storeConfig: {
filters: queryFilters
}
});
},
scope: this
});
}
});
To show the timebox filter, choose the type of filter you desire when first setting up your Custom Page in Rally. The "onTimeboxScopeChange" callback responds to events triggered by the timebox selector setup and configured on the Custom Page container itself. No code is needed to setup the timebox selector, rather you do it via the Rally UI when creating a new Custom Page:
Select the type of filter (Release or Iteration):
(1) Note that the filter shows on the "My New Custom Page" Container. (2) Any app that you add to "My New Custom Page" will then have the timebox filter available/applied:
Add Custom HTML App:
Paste in Code and Save:
Iteration-filtered Grid:
Alternatively, if you don't want a timebox filter that applies to an entire Custom Page container, you can elect to use Rally.ui.combobox.ReleaseComboBox or Rally.ui.combobox.IterationComboBox
In your App code itself, and manage the filtering via callbacks from either of these components. This filtering would be totally "within-app" and wouldn't rely on the Custom Page-wide timebox component.