Related
Try to change the columns list dynamically via a query ...
When I construct the TreeList, I call for columns :
$("#treelist").kendoTreeList({
columns: AnalyseCenterSKUService.getKPIColumnList($scope)
If I return a simple array with the fields, it's working ..
If I call a $http.get (inside my getKPIColumnList(..) function) which add some columns to the existing array of columns, the TreeList is not constructed correctly.
Any suggestion will be really appreciated ! :)
EDIT 22-10-2019 09:00
Treelist init
$("#treelist").kendoTreeList({
columns: AnalyseCenterSKUService.getKPIColumnList($scope),
scrollable: true,
columnMenu : {
columns : true
},
height: "100%",
dataBound: function (e) {
ExpandAll();
},
dataSource: {
schema: {
model: {
id: "id",
parentId: "parentId",
fields: {
id: { type: "number" },
parentId: { type: "number", nullable: true },
fields: {
id: { type: "number" },
parentId: { type: "number", nullable: false }
}
}
}
},
transport: {
read: {
url: "/api/AnalyseCenter/GetWorkOrderTree/0",
dataType: "json"
}
}
}
The getKPIColumnList return an static array + some push with dynamic columns (from DB)
angular.module('AnalyseCenterDirectives')
.service ('AnalyseCenterSKUService', function ($http) {
var toReturn = [ {field: "Name", title: "Hiérachie SKU", width: "30%" }, ..., ..., .... ];
I try in this function to push DB result
return $http.get("/api/AnalyseCenter/GetWorkOrderHistorianAdditonalColumns?equipmentName=" + equipmentName)
.then(function (result) {
var data = result.data;
if (data && data !== 'undefined') {
var fromDB = data;
angular.forEach(fromDB, function (tag) {
var tagName = tag.replace(".","_");
toReturn.push({
field: tagName, title: tag, width: '10%',
attributes: { style: "text-align:right;"} })
})
The stored procedure GetWorkOrderHistorianAdditonalColumns returns a list of string (future column)
That is because ajax is async, that means your tree list is being initialized before the request finishes. A classic question for JavaScript newcomers. I suggest you take a while to read about ajax, like How does AJAX works for instance.
Back to your problem. You need to create your tree list inside the success callback(I can't give you a more complete solution since I don't know what you're doing inside your function or which framework you're using to open that ajax request) with the result data, which is probably your columns. Then it would work as if you're initializing it with arrays.
I have a many to many relationship between: Step and Control Through ControlsConfig.
When creating a Control object and call addStep function and specify the additional attributes (which exist in the relation table), Sequelize creates the records in the relational table ControlsConfig but the additional attributes are NULLs.
PS: The tables are creating correctly in the database.
Table 1: Step
Table 2: Control
Relation table: ControlsConfig
Step
var Step = sequelize.define('Step', {
title: { type: DataTypes.STRING, allowNull: false },
description: DataTypes.STRING,
type: { type: DataTypes.ENUM('task', 'approval'), allowNull: false, defaultValue: 'task' },
order: DataTypes.INTEGER
});
Step.associate = function(models) {
models.Step.belongsTo(models.User);
models.Step.belongsTo(models.Template);
models.Step.hasMany(models.Action);
};
Control
var Control = sequelize.define('Control', {
label: { type: DataTypes.STRING, allowNull: false },
order: { type: DataTypes.INTEGER },
type: { type: DataTypes.ENUM('text', 'yes/no') },
config: { type: DataTypes.TEXT },
controlUiId: { type: DataTypes.STRING }
});
Control.associate = function(models) {
models.Control.belongsTo(models.Section);
};
ControlsConfigs
module.exports = (sequelize, DataTypes) => {
var ControlsConfig = sequelize.define('ControlsConfig', {
visibility: { type: DataTypes.ENUM('hidden', 'readonly', 'editable', 'required') },
config: { type: DataTypes.TEXT }
});
ControlsConfig.associate = function(models) {
models.Control.belongsToMany(models.Step, { through: models.ControlsConfig });
models.Step.belongsToMany(models.Control, { through: models.ControlsConfig });
models.ControlsConfig.belongsTo(models.Template);
};
return ControlsConfig;
};
Insertion:
try {
var step1 = await Step.create({ /*bla bla*/ });
var control1 = await Control.create({ /*bla bla*/ });
var OK = await control1.addStep(step1, {through: { config: 'THIS FIELD ALWAYS APPEARS NULL' }});
} catch (error) { /* No errors*/ }
I am following the same strategy stated at the documentation
//If you want additional attributes in your join table, you can define a model for the join table in sequelize, before you define the association, and then tell sequelize that it should use that model for joining, instead of creating a new one:
const User = sequelize.define('user', {})
const Project = sequelize.define('project', {})
const UserProjects = sequelize.define('userProjects', {
status: DataTypes.STRING
})
User.belongsToMany(Project, { through: UserProjects })
Project.belongsToMany(User, { through: UserProjects })
//To add a new project to a user and set its status, you pass extra options.through to the setter, which contains the attributes for the join table
user.addProject(project, { through: { status: 'started' }})
You have to pass edit: true to the addProject and addStep method.
See this answer it has a similar issue
Sequelize belongsToMany additional attributes in join table
I'm trying to use the Rally 2.1 SDK to set a custom data field (c_wsjf) in a grid. I have a custom drop down list that I want to check the value of (c_TimeCrticalitySizing).
I created c_TimeCrticalitySizing as a feature card field in my Rally workspace with different string values (such as "No decay"). Every drop down list value will set the custom field to a different integer. When I try to run the app in Rally I get this error:
"Uncaught TypeError: Cannot read property 'isModel' of undefined(…)"
I'm thinking the drop down list value may not be a string.
How would I check what the type of the drop down list value is?
How could I rewrite this code to correctly check the value of the drop down list so I can set my custom field to different integers?
Here's my code block for the complete app. I'm still trying to hook up a search bar so for now I directly call _onDataLoaded() from the launch() function.
// START OF APP CODE
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
featureStore: undefined,
featureGrid: undefined,
items: [ // pre-define the general layout of the app; the skeleton (ie. header, content, footer)
{
xtype: 'container', // this container lets us control the layout of the pulldowns; they'll be added below
itemId: 'widget-container',
layout: {
type: 'hbox', // 'horizontal' layout
align: 'stretch'
}
}
],
// Entry point of the app
launch: function() {
var me = this;
me._onDataLoaded();
},
_loadSearchBar: function() {
console.log('in loadsearchbar');
var me = this;
var searchComboBox = Ext.create('Rally.ui.combobox.SearchComboBox', {
itemId: 'search-combobox',
storeConfig: {
model: 'PortfolioItem/Feature'
},
listeners: {
ready: me._onDataLoaded,
select: me._onDataLoaded,
scope: me
}
});
// using 'me' here would add the combo box to the app, not the widget container
this.down('#widget-container').add(searchComboBox); // add the search field to the widget container <this>
},
// If adding more filters to the grid later, add them here
_getFilters: function(searchValue){
var searchFilter = Ext.create('Rally.data.wsapi.Filter', {
property: 'Search',
operation: '=',
value: searchValue
});
return searchFilter;
},
// Sets values once data from store is retrieved
_onDataLoaded: function() {
console.log('in ondataloaded');
var me = this;
// look up what the user input was from the search box
console.log("combobox: ", this.down('#search-combobox'));
//var typedSearch = this.down('#search-combobox').getRecord().get('_ref');
// search filter to apply
//var myFilters = this._getFilters(typedSearch);
// if the store exists, load new data
if (me.featureStore) {
//me.featureStore.setFilter(myFilters);
me.featureStore.load();
}
// if not, create it
else {
me.featureStore = Ext.create('Rally.data.wsapi.Store', {
model: 'PortfolioItem/Feature',
autoLoad: true,
listeners: {
load: me._createGrid,
scope: me
},
fetch: ['FormattedID', 'Name', 'TimeCriticality',
'RROEValue', 'UserBusinessValue', 'JobSize', 'c_TimeCriticalitySizing']
});
}
},
// create a grid with a custom store
_createGrid: function(store, data){
var me = this;
var records = _.map(data, function(record) {
//Calculations, etc.
console.log(record.get('c_TimeCriticalitySizing'));
var timecritsize = record.get('c_TimeCriticalitySizing');
//console.log(typeof timecritsize);
var mystr = "No decay";
var jobsize = record.get('JobSize');
var rroe = record.get('RROEValue');
var userval = record.get('UserBusinessValue');
var timecrit = record.get('TimeCriticality');
// Check that demoniator is not 0
if ( record.get('JobSize') > 0){
if (timecritsize === mystr){
var priorityScore = (timecrit + userval + rroe) / jobsize;
return Ext.apply({
c_wsjf: Math.round(priorityScore * 10) / 10
}, record.getData());
}
}
else{
return Ext.apply({
c_wsjf: 0
}, record.getData());
}
});
// Add the grid
me.add({
xtype: 'rallygrid',
showPagingToolbar: true,
showRowActionsColumn: true,
enableEditing: true,
store: Ext.create('Rally.data.custom.Store', {
data: records
}),
// Configure each column
columnCfgs: [
{
xtype: 'templatecolumn',
text: 'ID',
dataIndex: 'FormattedID',
width: 100,
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'WSJF Score',
dataIndex: 'c_wsjf',
width: 150
},
{
text: 'Name',
dataIndex: 'Name',
flex: 1,
width: 100
}
]
});
}
});
// END OF APP CODE
The app works great until I add the if (timecritsize === mystr) conditional.
I also use console.log() to check that I've set all values for timecritsize to "No decay"
I have a foreign key inside my kendo grid, and I have created an editor for this foreign key. It's working Ok in saving, but the problem is that when the grid displays data, the foreign key value is undefined. I know I have to change the template to show correct value. I have added the function intemplate to show the correct value, but it's not working for me.
Can you help me please? Here is my code:
var collection;
$("#mygrid").kendoGrid({
dataSource: dataSource,
pageable: true,
toolbar: [{ name: 'create', text: 'My create' }],
columns: [
{ field: "ForeignKeyColumn", editor: categoryDropDownEditor, template: "#=getTextByValue(data)#" },
{ field: "NOTES", title: "Notes" },
{ command: ["edit", "destroy"], title: " ", width: "160px" }],
editable: "popup",
});
//update model when choose value from dropdown list
var grid = $("#mygrid").data("kendoGrid");
grid.bind("save", grid_save);
function grid_save(e) {
if (e.model.ForeignKey) {
//change the model value
e.model.ForeignKeyColumn = 0;
//get the currently selected value from the DDL
var currentlySelectedValue = $(e.container.find("#typeCombo")[0]).data().kendoDropDownList.value();
//set the value to the model
e.model.set('ForeignKeyColumn', currentlySelectedValue);
}
}
//Editor template
function categoryDropDownEditor(container, options) {
$('<input id="typeCombo" required data-text-field="text" data-value-field="value" data-bind="value:' + options.field + '"/>')
.appendTo(container)
.kendoDropDownList({
autoBind: true,
dataSource: {
type: "json",
transport: {
read: {
url: '#Url.Action("SomeAction", "SomeController")',
type: "POST",
contentType: "application/json",
}
}
}
});
}
//Show template
function getTextByValue(data) {
//if the collection is empty - get it from the grid
if (!collection) {
grid = $("#GridName").data("kendoGrid");
valuesCollection = grid.options.columns[1].values;//Set the correct FKColumn index
collection = {};
for (var value in valuesCollection) {
collection[valuesCollection[value].value] = valuesCollection[value].text;
}
}
return collection[data.ForeignKeyColumn];
}
Note: valuesCollection value is undefined when I trace.
Try this,
I think it's necessary for binding foreign key column in grid. Please do not change the .cshtml view name or folder name.
Store in this location:
Location: - YourViews -> Shared -> EditorTemplates -> GridForeignKey.cshtml
GridForeignKey.cshtml:
#(Html.Kendo().DropDownListFor(m => m)
.Name(ViewData.TemplateInfo.GetFullHtmlFieldName(""))
.BindTo((SelectList)ViewData[ViewData.TemplateInfo.GetFullHtmlFieldName("") + "_Data"])
.OptionLabel("--Not Category--")
)
I'm currently trying to write a Rally app that will display the Predecessor/Successor hierarchy for a selected User Story. To illustrate, the user will select a User Story from a Chooser UI element. Then, a three-column Cardboard UI element will be generated--the leftmost column will contain all of the selected User Story's Predecessors (in card form), the middle column will contain the selected User Story's card, and the rightmost column will contain all of the selected User Story's Successors (in card form). From there, the Predecessor and Successor cards can be removed (denoting that they won't be Predecessors or Successors for the selected User Story) and new Predecessor/Successor cards can be added (denoting that they will become new Predecessors/Successors for the selected User Story).
However, my issue is this: the Cardboard UI was designed to display sets of different values for one particular attribute, but "Predecessor" and "Successor" don't fall into this category. Is there a possible way for me to display a User Story and then get its Predecessors and Successors and populate the rest of the board? I realize that it will take a substantial amount of modifications to the original board.
I've found a way to hack it so that you can do the display. Not sure if it's worth it or not and what you want to do for adding/removing, but this might help set you on the right path.
In summary, the problem is that the cardboard/column classes are designed to work with single value fields and each column that's created does an individual query to Rally based on the column configuration. You'll need to override the rallycardboard and the rallycolumn. I'll give the full html that you can paste in below, but let's hit this one piece at a time.
If nothing else, this might be a good example of how to take the source code of rally classes and make something to override them.
Data:
The existing cardboard is given a record type and field and runs off to create a column for each valid value for the field. It gets this information by querying Rally for the stories and for the attribute definitions. But we want to use our data in a slightly different way, so we'll have to make a different data store and feed it in. So we want to use a wsapidatastore to go ahead and get the record we asked for (in this example, I have a story called US37 that has predecessors and successors). In a way, this is like doing a cross-tab in Excel. Instead of having one record (37) that's related to others, we want to make a data set of all the stories and define their relationship in a new field, which I called "_column." Like this:
Ext.create('Rally.data.WsapiDataStore', {
model: "hierarchicalrequirement",
autoLoad: true,
fetch: ['Name','Predecessors','Successors','FormattedID','ObjectID','_ref'],
filters: [ {
property: 'FormattedID', operator: 'contains', value: '37'
} ] /* get the record US37 */,
listeners: {
load: function(store,data,success) {
if ( data.length === 1 ) {
var base_story = data[0].data;
var modified_records = [];
base_story._column = "base";
modified_records.push( base_story );
Ext.Array.each( base_story.Predecessors, function( story ) {
story._column = "predecessor";
modified_records.push( story );
} );
Ext.Array.each( base_story.Successors, function(story) {
story._column = "successor";
modified_records.push( story );
} );
We push the data into an array of objects with each having a new field to define which column it should go in. But this isn't quite enough, because we need to put the data into a store. The store needs a model -- and we have to define the fields in a way that the cardrenders know how to access the data. There doesn't seem to be an easy way to just add a field definition to an existing rally model, so this should do it (the rally model has unique field information and a method called getField(), so we have to add that:
Ext.define('CardModel', {
extend: 'Ext.data.Model',
fields: [
{ name: '_ref', type: 'string' },
{ name: 'ObjectID', type: 'number'},
{ name: 'Name', type: 'string', attributeDefinition: { AttributeType: 'STRING'} },
{ name: 'FormattedID', type: 'string'},
{ name: '_column', type: 'string' },
{ name: 'ScheduleState', type: 'string' } ] ,
getField: function(name) {
if ( this.data[name] ) {
var return_field = null;
Ext.Array.each( this.store.model.getFields(), function(field) {
if ( field.name === name ) {
return_field = field;
}
} );
return return_field;
} else {
return null;
}
}
});
var cardStore = Ext.create('Ext.data.Store',{
model: 'CardModel',
data: modified_records
});
Now, we'll create a cardboard, but instead of the rally cardboard, we'll make one from a class we're going to define below ('DependencyCardboard'). In addition, we'll pass along a new definition for the columns that we'll also define below ('dependencycolumn').
var cardboard = Ext.create('DependencyCardboard', {
attribute: '_column',
store: cardStore, /* special to our new cardboard type */
height: 500,
columns: [{
xtype: 'dependencycolumn',
displayValue: 'predecessor',
value: 'predecessor',
store: cardStore
},
{
xtype: 'dependencycolumn',
displayValue: 'base',
value: 'base',
store: cardStore
},
{
xtype: 'dependencycolumn',
displayValue: 'successor',
value: 'successor',
store: cardStore
}]
});
Cardboard:
Mostly, the existing Rally cardboard can handle our needs because all the querying is done down in the columns themselves. But we still have to override it because there is one function that is causing us problems: _retrieveModels. This function normally takes the record type(s) (e.g., User Story) and based on that creates a data model that is based on the Rally definition. However, we're not using the UserStory records directly; we've had to define our own model so we could add the "_columns" field. So, we make a new definition (which we use in the create statement above for "DependencyCardboard").
(Remember, we can see the source code for all of the Rally objects in the API just by clicking on the title, so we can compare the below method to the one in the base class.)
We can keep all of the stuff that the Rally cardboard does and only override the one method by doing this:
Ext.define( 'DependencyCardboard', {
extend: 'Rally.ui.cardboard.CardBoard',
alias: 'widget.dependencycardboard',
constructor: function(config) {
this.mergeConfig(config);
this.callParent([this.config]);
},
initComponent: function() {
this.callParent(arguments);
},
_retrieveModels: function(success) {
if ( this.store ) {
this.models = [ this.store.getProxy().getModel() ];
success.apply( this, arguments );
}
}
});
Column:
Each column normally goes off to Rally and says "give me all of the stories that have a field equal to the column name". But we're passing in the store to the cardboard, so we need to override _queryForData. In addition, there is something going on about defining the column height when we do this (I don't know why!) so I had to add a little catch in the getColumnHeightFromCards() method.
_queryForData: function() {
var allRecords = [];
var records = this.store.queryBy( function( record ) {
if ( record.data._column === this.getValue() ) { allRecords.push( record ); }
}, this);
this.createAndAddCards( allRecords );
},
getColumnHeightFromCards: function() {
var contentMinHeight = 500,
bottomPadding = 30,
cards = this.query(this.cardConfig.xtype),
height = bottomPadding;
for(var i = 0, l = cards.length; i < l; ++i) {
if ( cards[i].el ) {
height += cards[i].getHeight();
} else {
height += 100;
}
}
height = Math.max(height, contentMinHeight);
height += this.down('#columnHeader').getHeight();
return height;
}
Finish
So, if you add all those pieces together, you get one long html file that we can push into a panel (and that you can keep working on to figure out how to override dragging results and to add your chooser panel for the first item. (And you can make better abstracted into its own class)).
Full thing:
<!DOCTYPE html>
<html>
<head>
<title>cardboard</title>
<script type="text/javascript" src="/apps/2.0p3/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function() {
/*global console, Ext */
Ext.define( 'DependencyColumn', {
extend: 'Rally.ui.cardboard.Column',
alias: 'widget.dependencycolumn',
constructor: function(config) {
this.mergeConfig(config);
this.callParent([this.config]);
},
initComponent: function() {
this.callParent(arguments);
},
_queryForData: function() {
var allRecords = [];
var records = this.store.queryBy( function( record ) {
if ( record.data._column === this.getValue() ) { allRecords.push( record ); }
}, this);
this.createAndAddCards( allRecords );
},
getColumnHeightFromCards: function() {
var contentMinHeight = 500,
bottomPadding = 30,
cards = this.query(this.cardConfig.xtype),
height = bottomPadding;
for(var i = 0, l = cards.length; i < l; ++i) {
if ( cards[i].el ) {
height += cards[i].getHeight();
} else {
height += 100;
}
}
height = Math.max(height, contentMinHeight);
height += this.down('#columnHeader').getHeight();
return height;
}
});
/*global console, Ext */
Ext.define( 'DependencyCardboard', {
extend: 'Rally.ui.cardboard.CardBoard',
alias: 'widget.dependencycardboard',
constructor: function(config) {
this.mergeConfig(config);
this.callParent([this.config]);
},
initComponent: function() {
this.callParent(arguments);
},
_retrieveModels: function(success) {
if ( this.store ) {
this.models = [ this.store.getProxy().getModel() ];
success.apply( this, arguments );
}
}
});
/*global console, Ext */
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
items: [ { xtype: 'container', itemId: 'outer_box' }],
launch: function() {
Ext.create('Rally.data.WsapiDataStore', {
model: "hierarchicalrequirement",
autoLoad: true,
fetch: ['Name','Predecessors','Successors','FormattedID','ObjectID','_ref'],
filters: [ {
property: 'FormattedID', operator: 'contains', value: '37'
} ],
listeners: {
load: function(store,data,success) {
if ( data.length === 1 ) {
var base_story = data[0].data;
var modified_records = [];
base_story._column = "base";
modified_records.push( base_story );
Ext.Array.each( base_story.Predecessors, function( story ) {
story._column = "predecessor";
modified_records.push( story );
} );
Ext.Array.each( base_story.Successors, function(story) {
story._column = "successor";
modified_records.push( story );
} );
Ext.define('CardModel', {
extend: 'Ext.data.Model',
fields: [
{ name: '_ref', type: 'string' },
{ name: 'ObjectID', type: 'number'},
{ name: 'Name', type: 'string', attributeDefinition: { AttributeType: 'STRING'} },
{ name: 'FormattedID', type: 'string'},
{ name: '_column', type: 'string' },
{ name: 'ScheduleState', type: 'string' } ] ,
getField: function(name) {
if ( this.data[name] ) {
var return_field = null;
Ext.Array.each( this.store.model.getFields(), function(field) {
if ( field.name === name ) {
return_field = field;
}
} );
return return_field;
} else {
return null;
}
}
});
var cardStore = Ext.create('Ext.data.Store',{
model: 'CardModel',
data: modified_records
});
var cardboard = Ext.create('DependencyCardboard', {
attribute: '_column',
store: cardStore,
height: 500,
columns: [{
xtype: 'dependencycolumn',
displayValue: 'predecessor',
value: 'predecessor',
store: cardStore
},
{
xtype: 'dependencycolumn',
displayValue: 'base',
value: 'base',
store: cardStore
},
{
xtype: 'dependencycolumn',
displayValue: 'successor',
value: 'successor',
store: cardStore
}]
});
this.down('#outer_box').add( cardboard );
}
},
scope: this
}
});
}
});
Rally.launchApp('CustomApp', {
name: 'cardboard'
});
});
</script>
<style type="text/css">
.app {
/* Add app styles here */
}
</style>
</head>
<body></body>
</html>