I am trying to assign values to a multiselectable combo box in extjs property grid. When I assign some values to the field, the grid fails to render the field. How to resolve this ? Please find the code below.
Ext.create('Ext.grid.property.Grid', {
title: 'Properties Grid',
width: 400,
renderTo: Ext.getBody(),
source: {
name: "My Object",
created: Ext.Date.parse('10/15/2006', 'm/d/Y'),
timeofday: "12:00 PM",
available: false,
version: 0.01,
description: Ext.encode({
product: 'Clorox',
tagline: 'The Shinyest White!'
}),
childAccounts:['john','mike']
},
customEditors: {
timeofday: Ext.create('Ext.form.TimeField', {selectOnFocus: true}),
description: {
xtype: 'customeditorfield'
},
childAccounts:Ext.create('Ext.form.ComboBox', {store: ['john', 'mike', 'harvey'],multiSelect:true}),
},
customRenderers: {
timeofday: function( v ) {
return Ext.isDate( v ) ? Ext.Date.format( v, 'g:i A' ) : v;
}
},
propertyNames: {
name: '(name)',
created: 'Created Date',
timeofday: 'Time of Day',
available: 'Available?',
version: 'Version',
description: 'Product Description',
childAccounts: 'Child Description'
},
listeners: {
beforeedit: function( editor, e, opts ) {
if( e.record.get( 'name' )=='name' || e.record.get( 'name' )=='version' ) {
return false;
}
}
}
});
By default, only "editable" values are supported by property grid, and Sencha does not count arrays as editable. You can try to override this if you want. The limitation is introduced in Ext.grid.property.Store.getReader, where there is a function isEditableValue:
isEditableValue: function(val){
return Ext.isPrimitive(val) || Ext.isDate(val) || val === null;
}
If you change this function to allow for arrays, it seems to work, but without warranty. I used the following code for quick test:
Ext.define('', {
override: 'Ext.grid.property.Store',
getReader: function() {
var newReader = !this.reader;
this.callParent(arguments);
if(newReader) this.reader.isEditableValue = function(val) {
return Ext.isPrimitive(val) || Ext.isDate(val) || Ext.isArray(val) || val === null;
}
return this.reader;
}
});
Fiddle (Without warranty, as always)
Solution:
Your "childAccounts" config is not correct. Try to replace:
childAccounts:['john','mike']
with:
childAccounts: 'john, mike'
Notes:
Tested with ExtJS 4.2.
Related
I am trying to upload the file via Media Library from Inspect Control in Gutenberg. I am using this code currently in JS :
var el = wp.element.createElement,
registerBlockType = wp.blocks.registerBlockType,
ServerSideRender = wp.components.ServerSideRender,
TextControl = wp.components.TextControl,
TextareaControl = wp.components.TextareaControl,
MediaUpload = wp.components.MediaUpload,
InspectorControls = wp.editor.InspectorControls;
And here I am registering the block type :
registerBlockType( 'myplugin/about-section', {
title: 'About Section',
icon: 'megaphone',
category: 'widgets',
edit: function( props ) {
return [
el( ServerSideRender, {
block: 'malinda/about-section',
attributes: props.attributes,
} ),
el( InspectorControls, {},
el( MediaUpload, {
label: 'Background Image',
value: props.attributes.bgimg,
// I think something need to be done here..
} ),
),
];
},
save: function() {
return null;
},
} );
But for some reason it's not working for me. In console it's giving me this error :
Error: Minified React error #130; visit
https://reactjs.org/docs/error-decoder.html?invariant=130&args[]=undefined&args[]=
for the full message or use the non-minified dev environment for full
errors and additional helpful warnings.
and when I click on block it says :
The editor has encountered an unexpected error.
Can anyone please help me in that?
You need add attributes field for save image link. Then you need add MediaUpload element and add callback on click. And after save value. You can integrate my code in your
decision. I am add image select to inspector controls area.
( function( editor, components, i18n, element ) {
var el = element.createElement;
var registerBlockType = wp.blocks.registerBlockType;
var InspectorControls = wp.editor.InspectorControls;
var MediaUpload = wp.editor.MediaUpload;
registerBlockType( 'designa/image-block', {
title: 'Image block',
description: 'Image block.',
icon: 'video-alt3',
category: 'common',
attributes: {
mediaURL: {
type: 'string',
source: 'attribute',
selector: 'img',
attribute: 'src',
}
},
edit: function( props ) {
var attributes = props.attributes;
var onSelectImage = function( media ) {
return props.setAttributes({
mediaURL: media.url
});
};
return [
el( InspectorControls, { key: 'inspector' },
el( components.PanelBody, {
title: 'Image block',
className: 'image-block',
initialOpen: true,
},
el( MediaUpload, {
onSelect: onSelectImage,
type: 'image',
render: function( obj ) {
return el( components.Button, {
className: 'components-icon-button image-block-btn is-button is-default is-large',
onClick: obj.open
},
el( 'svg', { className: 'dashicon dashicons-edit', width: '20', height: '20' },
el( 'path', { d: "M2.25 1h15.5c.69 0 1.25.56 1.25 1.25v15.5c0 .69-.56 1.25-1.25 1.25H2.25C1.56 19 1 18.44 1 17.75V2.25C1 1.56 1.56 1 2.25 1zM17 17V3H3v14h14zM10 6c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm3 5s0-6 3-6v10c0 .55-.45 1-1 1H5c-.55 0-1-.45-1-1V8c2 0 3 4 3 4s1-3 3-3 3 2 3 2z" } )
),
el( 'span', {},
'Select image'
),
);
}
}),
),
)
];
},
save: function( props ) {
var attributes = props.attributes;
return (
el( 'div', {
className: props.className
},
el( 'img', {
src: attributes.mediaURL
})
)
);
},
} );
} )(
window.wp.editor,
window.wp.components,
window.wp.i18n,
window.wp.element,
);
Tried typeof item.type === '<type>' also instanceof none work as expected.
Background: I'm building a form generator (using VueJS).
This is the form definition:
formDesc: {
inline: true,
columns: 2,
labels: 'top',
disabled: false,
fields: [
{type: Array, prop: 'aprovalStatus', list: ['Pending', 'Rejected', 'Conditional', 'Approved'], label: 'Status', required: true},
{type: String, prop: 'remarks', html: true, label: 'Remarks'},
{type: Date, prop: 'approvalDate', label: 'Approval Date', format: 'DD-MM-YYYY'},
{type: Number, prop: 'deposit', decimals: 2, label: 'Security Deposit'}
]
}
The problem I'm having is determining form.fields.<item>.type on the form generator side. The parameter is received fine. To provide the correct input type, I need to know what the form item type is!
I have the following method (from one of the Google searches):
getType (elem) {
return Object.prototype.toString.call(elem).slice(8, -1).toLowerCase()
}
Which as I read it, should return 'number', 'date' etc.
Despite what I've read, this also returns 'function' just like typeof item.type does.
Source article
in the form generator code I have methods to determine type:
...
check (item) {
if (item && item.type) {
console.log('getType = ', this.getType(item.type))
return true
} else {
this.$message({message: 'Field item problem [' + !item ? 'Empty item' : 'no type prop for: ' + (item && item.label ? item.label : 'No label') + ']', type: 'warning'})
return false
}
},
isArray (item) {
if (this.check(item) && this.getType(item.type) === 'array' &&
item.hasOwnProperty('list')) {
return true
} else {
return false
}
},
...
isArray, isNumber, is Decimal etc. are used to determine which component to show for each element in formDesc.fields.
My other attempts were item.type.constructor === Number
Any links or solutions for this greatly appreciated.
Silly me. This works item.type === Number (or relevant primitive data type)
I want to add some filters to my grid when I clicked on the filter button (see below).The filters must be add tot the grid with the given values from the filterpanel form.
On this page the textfield will be filled in and when I clicked on the filter button the onFilterClick handler will trigger the FilterController.
Ext.define('path.to.filterPanel', {
extend: 'Ext.form.Panel',
xtype: 'filter',
controller: 'dashboard-filter',
viewModel: {
type: 'dashboard-filter'
},
frame: false,
bodyPadding: 10,
layout: 'form',
// the fields
items: [{
xtype: 'textfield',
name: 'firstName',
id: 'firstName',
fieldLabel: 'Firstname'
}, {
xtype: 'textfield',
name: 'lastName',
id: 'lastName',
fieldLabel: 'Lastname'
}],
buttons: [
text : 'Filter',
handler: 'onFilterClick' // trigger to the controller
}]
});
When clicked on the Filterbutton the values will be pust to this controller.
Ext.define('path.to.FilterController', {
extend: 'Ext.app.ViewController',
alias: 'controller.dashboard-filter',
onFilterClick: function(button) {
var form = button.up('form').getForm();
if (form.isValid()) {
// form valid
var firstName = Ext.getCmp("firstName").getValue();
var lastName = Ext.getCmp("lastName").getValue();
// check if there is some input
if (!!employeeNumber || !!firstName || !!lastName) {
// add filters but how??
}
} else {
// form not valid
console.log("not valid");
}
}
});
Finally this is the grid file where the filters must be added.
Ext.define('path.to.gridPanel, {
extend: 'Ext.grid.Panel',
requires: [
'Ext.grid.column.Action',
'App.store.Employees'
],
controller: 'dashboard-gridVieuw',
xtype: 'gridVieuw',
store: 'Employees',
stateful: true,
collapsible: true,
multiSelect: true,
stateId: 'gridController',
initComponent: function () {
this.store = new App.store.Employees();
var me = this;
me.columns = [{
header : 'Firstname',
flex : 1,
sortable : true,
dataIndex: 'firstName'
}, {
header : 'Lastname',
flex : 1,
sortable : true,
dataIndex: 'lastName'
}]
me.callParent();
}
});
I use version 5 of extjs.
You can use filterBy method to filter the underlying store associated with the grid. Here is an example:
Ext.getStore('myStore').filterBy(function(record, id) {
if (record.get('firstName') === firstName) {
return true;
} else {
return false;
}
});
Here is a fiddle demonstrating a working example of a filter
Thanks for answering my question. It works for me. I've added the follow OnClick handler in the controller.
onFilterClick: function(button) {
var form = button.up('form').getForm();
if (form.isValid()) {
var fieldOne = Ext.getCmp("fieldOne").getValue();
var fieldTwo = Ext.getCmp("fieldTwo").getValue();
// check if there is some input
if (!!fieldOne || !!fieldTwo) {
// get store
var store = Ext.data.StoreManager.lookup('store');
// check if store not empty
if(!Ext.isEmpty(store)){
// clear filters and add a few new filters if params not empty
store.clearFilter(true);
if (!Ext.isEmpty(fieldOne)) {
store.filter("fieldOne", fieldOne)
}
if (!Ext.isEmpty(fieldTwo)) {
store.filter("fieldTwo", fieldTwo)
}
// count the filtered rows
var count = store.snapshot ? store.snapshot.length : store.getCount();
if (count == 0) {
alert("No data found!");
store.clearFilter();
}
} else{
//Store empty
console.warn("Store's empty");
}
} else {
// no values
alert("No entered data!");
}
} else {
// form not valid
alert("Form not valid!");
}
}
The following code describe the problem:
var store = Ext.create('Ext.data.Store', {
fields: ['Name'],
data: [
{
Name: 'Java'
},
{
Name: 'C'
},
{
Name: 'Android'
}
]
});
store.insert(0, [Ext.create(store.model, {
Name: ''
})]);
Ext.create('widget.combobox', {
renderTo: Ext.getBody(),
margin: '10',
width: 500,
store: store,
displayField: 'Name',
valueField: 'Name',
queryMode: 'local'
listeners: {
beforeselect: function (cbo, rec, idx) {
console.log(idx);
}
}
});
The new data being inserted to the store using 'insert' method doesn't have index in combobox. Every time I click on the blank record, I see the log of idx is 'undefined'. My expectation is '0'. How could I fix this?
Good find, that's most probably a bug in Ext. The (undocumented) record index is set in loadData and loadRawData functions, and it is not updated with inserted records.
Don't rely on this buggy implementation and work around it by using the calculated store index:
beforeselect: function (cbo, rec) {
var idx = cbo.getStore().indexOf(rec);
console.log(idx);
}
I found some code on GitHub (https://github.com/RallyCommunity/TagCloud) for displaying a TagCloud in Rally. Conceptually it looks great but I cannot seem to get it to work and wondered if there were any Rally JavaScript experts out there who could take a quick look.
I modified it slightly as the URL for the Analytics was incorrect (according to the docs) and the API's were hard coded to depreciated versions so I updated those.
I'm not a JavaScript expert. When I run this it doesn't find any Tags against stories.
When I run this in Chrome debugging mode I can take the URL and execute it in my browser but it also does not come back with any Tags. It comes back with a full result response from Analytics, but has no tags and I know there are tags against stories in the selected project.
Any ideas from anyone?
<!DOCTYPE html>
<html>
<head>
<title>TagCloud</title>
<script type="text/javascript" src="/apps/2.0p/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function() {
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
layout: 'border',
items: [
{
title: 'Tag Cloud',
xtype: 'panel',
itemId: 'cloud',
region: 'west',
width: '30%',
collapsible: true,
bodyStyle: 'padding:15px',
listeners: { 'afterRender': function(el) { setTimeout(function() { el.setLoading(); }, 500);}} // needs a little delay
},
{
title: '<<< Select a tag from the Tag Cloud',
xtype: 'panel',
itemId: 'grid',
region: 'center',
width: '70%',
collapsible: false
}
],
tagMap : [],
maxFont : 24, // largest desired font size
minFont : 8, // and smallest
_renderTag : function renderHandler(tagLabel) {
tagLabel.getEl().on('click',this._tagSelected, this);
},
// does the actual building of the cloud from 'tagMap'
_buildCloud: function(app, response) {
//title: '<<< Select a tag from the Tag Cloud - Building',
var i, tag;
for (i=0;i<response.Results.length;i++) {
tag = response.Results[i];
if(typeof app.tagMap[tag.ObjectID] !== "undefined") {
app.tagMap[tag.ObjectID].Name = tag._refObjectName;
}
}
if(response.StartIndex+response.PageSize < response.TotalResultCount) {
app._queryForTagNames(response.StartIndex+response.PageSize, app, app._buildCloud);
} else {
if(app.tagMap.length === 0) {
tag = new Ext.form.Label({
id: 'tagNone',
text: ' No tagged Stories found '
});
app.down('#cloud').add(tag);
} else {
var minFrequency = Number.MAX_VALUE;
var maxFrequency = Number.MIN_VALUE;
var tuples = [];
for (var x in app.tagMap) {
if (app.tagMap.hasOwnProperty(x)) {
tuples.push([x, app.tagMap[x]]);
if(app.tagMap[x].count > maxFrequency) {
maxFrequency = app.tagMap[x].count;
}
if(app.tagMap[x].count < minFrequency) {
minFrequency = app.tagMap[x].count;
}
}
}
tuples.sort(function(a,b) { a = a[1]; b = b[1]; return a.Name > b.Name ? 1 : a.Name < b.Name ? -1 : 0 ;});
for (i = 0; i < tuples.length; i++) {
var ftsize = ((tuples[i][1].count-minFrequency)*(app.maxFont-app.minFont) / (maxFrequency-minFrequency)) + app.minFont;
tag = new Ext.form.Label({
id: 'tag'+tuples[i][0],
text: ' ' + tuples[i][1].Name + ' ',
overCls: 'link',
style:"font-size: "+ftsize+"pt;",
listeners: { scope: app, render: app._renderTag }
});
app.down('#cloud').add(tag);
}
}
app.getComponent('cloud').setLoading(false);
}
},
// collects the _queryForTags responses and calls _queryForTagNames when it has them all
_buildTagMap: function(app, response) {
for (var i=0;i<response.Results.length;i++) {
var ent = response.Results[i];
for (var j=0; j < ent.Tags.length; j++) {
var tag = ent.Tags[j];
var mapent = app.tagMap[tag];
if(typeof mapent === "undefined") {
mapent = { count: 1 };
} else {
mapent.count++;
}
app.tagMap[tag] = mapent;
}
}
if(response.StartIndex+response.PageSize < response.TotalResultCount) {
app._queryForTags(response.StartIndex+response.PageSize, app, app._buildTagMap);
} else {
app._queryForTagNames(0, app, app._buildCloud);
}
},
// get a list of the tags from the Lookback API, iterating if necessary (see _buildTagMap)
_queryForTags: function(start, app, callback) {
var params = {
find: "{'Tags':{'$exists':true}, '__At':'current', '_Type':'HierarchicalRequirement', '_ProjectHierarchy':"+ this.getContext().getProject().ObjectID +" }",
fields: "['Tags']",
pagesize: 20000,
start: start
};
Ext.Ajax.request({
url: 'https://rally1.rallydev.com/analytics/v2.0/service/rally/workspace/'+ this.context.getWorkspace().ObjectID + '/artifact/snapshot/query.js',
method: 'GET',
params: params,
withCredentials: true,
success: function(response){
var text = response.responseText;
var json = Ext.JSON.decode(text);
callback(app, json);
}
});
},
// once all the tags have been collected, get a list of the tag names from the WSAPI, iterating if necessary (see _buildCloud)
_queryForTagNames: function(start, app, callback) {
Ext.Ajax.request({
url: 'https://rally1.rallydev.com/slm/webservice/1.41/tag.js',
method: 'GET',
params: { fetch: "ObjectID", pagesize: 200, "start": start},
withCredentials: true,
success: function(response){
callback(app, Ext.JSON.decode(response.responseText).QueryResult);
}
});
},
_queryForStories: function(tagOid) {
Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
autoLoad: true,
fetch: ['Rank', 'FormattedID', 'Name', 'ScheduleState'],
filters: [{
property:'Tags',
operator: '=',
value: "tag/" + tagOid
}],
sorters: [{
property: 'Rank',
direction: 'ASC'
}],
listeners: {
load: this._onDataLoaded,
scope: this
}
});
},
_tagSelected: function(app, elem) {
this.getComponent('grid').setLoading();
this._queryForStories(elem.id.substring(3)); // cheesy, id is "tag"+tagOid, we need the oid
this.tagName = elem.innerText;
},
_onDataLoaded: function(store, data) {
var records = [], rankIndex = 1;
Ext.Array.each(data, function(record) {
records.push({
Ranking: rankIndex,
FormattedID: record.get('FormattedID'),
Name: record.get('Name'),
State: record.get('ScheduleState')
});
rankIndex++;
});
var customStore = Ext.create('Rally.data.custom.Store', {
data: records,
pageSize: 25
});
if(!this.grid) {
this.grid = this.down('#grid').add({
xtype: 'rallygrid',
store: customStore,
columnCfgs: [
{ text: 'Ranking', dataIndex: 'Ranking' },
{ text: 'ID', dataIndex: 'FormattedID' },
{ text: 'Name', dataIndex: 'Name', flex: 1 },
{ text: 'State', dataIndex: 'State' }
]
});
} else {
this.grid.reconfigure(customStore);
}
this.getComponent('grid').setTitle('Stories tagged: ' + this.tagName);
this.getComponent('grid').setLoading(false);
},
launch: function() {
this._queryForTags(0, this, this._buildTagMap);
}
});
Rally.launchApp('CustomApp', {
name: 'TagCloud'
});
});
</script>
<style type="text/css">
.app {
}
.link {
color: #066792;
cursor: pointer;
} </style>
</head>
<body></body>
</html>
The code was out-of-date with respect to the most-recent LBAPI changes - specifically the use of _Type vs _TypeHierarchy and of course, the url, as you'd already discovered. Please pick up the changes and give it a whirl.