Creating a custom rally grid from source using app-catalog on github - rally

I saw that Rally made available its custom grid in github, so I downloaded the Rally app-catalog from github. I then went into the src/apps/grid directory and ran 'rally-app-builder build'. That created the deploy directory and the following code in App.html:
<!DOCTYPE html>
<html>
<head>
<title>Custom Grid</title>
<script type="text/javascript" src="/apps/2.0rc2/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function () {
(function(){var Ext=window.Ext4||window.Ext,appAutoScroll=Ext.isIE7||Ext.isIE8,gridAutoScroll=!appAutoScroll;Ext.define("Rally.apps.grid.GridApp",{extend:"Rally.app.App",layout:"fit",requires:["Rally.data.util.Sorter","Rally.data.QueryFilter","Rally.ui.grid.Grid","Rally.ui.grid.plugin.PercentDonePopoverPlugin"],autoScroll:appAutoScroll,launch:function(){Rally.data.ModelFactory.getModel({type:this.getContext().get("objectType"),success:this._createGrid,scope:this})},_getFetchOnlyFields:function(){return["LatestDiscussionAgeInMinutes"]},_createGrid:function(model){var context=this.getContext(),pageSize=context.get("pageSize"),fetch=context.get("fetch"),columns=this._getColumns(fetch),gridConfig={xtype:"rallygrid",model:model,columnCfgs:columns,enableColumnHide:!1,enableRanking:!0,enableBulkEdit:Rally.environment.getContext().isFeatureEnabled("EXT4_GRID_BULK_EDIT"),autoScroll:gridAutoScroll,plugins:this._getPlugins(columns),storeConfig:{fetch:fetch,sorters:Rally.data.util.Sorter.sorters(context.get("order")),context:context.getDataContext(),listeners:{load:this._updateAppContainerSize,scope:this}},pagingToolbarCfg:{pageSizes:[pageSize]}};pageSize&&(pageSize-=0,isNaN(pageSize)||(gridConfig.storeConfig.pageSize=pageSize)),context.get("query")&&(gridConfig.storeConfig.filters=[Rally.data.QueryFilter.fromQueryString(context.get("query"))]),this.add(gridConfig)},_updateAppContainerSize:function(){if(this.appContainer){var grid=this.down("rallygrid");grid.el.setHeight("auto"),grid.body.setHeight("auto"),grid.view.el.setHeight("auto"),this.setSize({height:grid.getHeight()+_.reduce(grid.getDockedItems(),function(acc,item){return acc+item.getHeight()+item.el.getMargin("tb")},0)}),this.appContainer.setPanelHeightToAppHeight()}},_getColumns:function(fetch){return fetch?Ext.Array.difference(fetch.split(","),this._getFetchOnlyFields()):[]},_getPlugins:function(columns){var plugins=[];return Ext.Array.intersect(columns,["PercentDoneByStoryPlanEstimate","PercentDoneByStoryCount"]).length>0&&plugins.push("rallypercentdonepopoverplugin"),plugins}})})();
Rally.launchApp('Rally.apps.grid.GridApp', {
name:"Custom Grid",
parentRepos:""
});
});
</script>
</head>
<body></body>
</html>
...but when you paste that into Rally, it just produces and empty app (frame with no contents).
Am I missing something simple here? Are there some tweaks I need to make to get this to work?

I just remembered I fixed the custom grid up quite a bit after 2.0rc2 was cut. You can see that here in the RallySoftware/app-catalog repo.
(We only guarantee these apps work with the head revision of the sdk (version x) but in this case the grid correctly works with 2.0rc2 as well).
Note: it does not have a settings panel yet.
Below is the full html with some default settings filled in:
<!DOCTYPE html>
<html>
<head>
<title>Custom Grid</title>
<script type="text/javascript" src="/apps/2.0rc2/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function () {
(function () {
var Ext = window.Ext4 || window.Ext;
var appAutoScroll = Ext.isIE7 || Ext.isIE8;
var gridAutoScroll = !appAutoScroll;
Ext.define('Rally.apps.grid.GridApp', {
extend: 'Rally.app.App',
layout: 'fit',
requires: [
'Rally.data.util.Sorter',
'Rally.data.wsapi.Filter',
'Rally.ui.grid.Grid',
'Rally.data.ModelFactory',
'Rally.ui.grid.plugin.PercentDonePopoverPlugin'
],
config: {
defaultSettings: {
types: 'defect',
pageSize: 25,
fetch: 'FormattedID,Name,Priority,Severity'
}
},
autoScroll: appAutoScroll,
launch: function () {
var context = this.getContext(),
pageSize = this.getSetting('pageSize'),
fetch = this.getSetting('fetch'),
columns = this._getColumns(fetch);
this.add({
xtype: 'rallygrid',
columnCfgs: columns,
enableColumnHide: false,
enableRanking: true,
enableBulkEdit: context.isFeatureEnabled("EXT4_GRID_BULK_EDIT"),
autoScroll: gridAutoScroll,
plugins: this._getPlugins(columns),
context: this.getContext(),
storeConfig: {
fetch: fetch,
models: this.getSetting('types').split(','),
filters: this._getFilters(),
pageSize: pageSize,
sorters: Rally.data.util.Sorter.sorters(this.getSetting('order')),
listeners: {
load: this._updateAppContainerSize,
scope: this
}
},
pagingToolbarCfg: {
pageSizes: [pageSize]
}
});
},
onTimeboxScopeChange: function (newTimeboxScope) {
this.callParent(arguments);
this.down('rallygrid').filter(this._getFilters(), true, true);
},
_getFilters: function () {
var filters = [],
query = this.getSetting('query'),
timeboxScope = this.getContext().getTimeboxScope();
if (query) {
try {
query = new Ext.Template(query).apply({
user: Rally.util.Ref.getRelativeUri(this.getContext().getUser())
});
} catch (e) {
}
filters.push(Rally.data.wsapi.Filter.fromQueryString(query));
}
if (timeboxScope && _.every(this.getSetting('types').split(','), this._isSchedulableType, this)) {
filters.push(timeboxScope.getQueryFilter());
}
return filters;
},
_isSchedulableType: function (type) {
return _.contains(['hierarchicalrequirement', 'task', 'defect', 'defectsuite', 'testset'], type.toLowerCase());
},
_getFetchOnlyFields: function () {
return ['LatestDiscussionAgeInMinutes'];
},
_updateAppContainerSize: function () {
if (this.appContainer) {
var grid = this.down('rallygrid');
grid.el.setHeight('auto');
grid.body.setHeight('auto');
grid.view.el.setHeight('auto');
this.setSize({height: grid.getHeight() + _.reduce(grid.getDockedItems(), function (acc, item) {
return acc + item.getHeight() + item.el.getMargin('tb');
}, 0)});
this.appContainer.setPanelHeightToAppHeight();
}
},
_getColumns: function (fetch) {
if (fetch) {
return Ext.Array.difference(fetch.split(','), this._getFetchOnlyFields());
}
return [];
},
_getPlugins: function (columns) {
var plugins = [];
if (Ext.Array.intersect(columns, ['PercentDoneByStoryPlanEstimate', 'PercentDoneByStoryCount']).length > 0) {
plugins.push('rallypercentdonepopoverplugin');
}
return plugins;
}
});
})();
Rally.launchApp('Rally.apps.grid.GridApp', {
name: "Custom Grid"
});
});
</script>
</head>
<body></body>
</html>
Hopefully this gets you by for now. Look for a much improved custom grid app coinciding with the next public sdk release.

You are not missing anything. When we cut the next release it will be working, but right now the app has not been toggled on.
What makes it empty is that type below does not resolve:
Rally.data.ModelFactory.getModel({
type: this.getContext().get('objectType'),
success: this._createGrid,
scope: this
});
unless you hard code it instead, as in type: 'defect' . Unfortunately this is not enough to fix it. The app is not ready to be used yet.

Related

Iteration.StartDate not available

When I query user stories using a Rally.data.WsapiDataStore, I can retrieve the Iteration.StartDate and Iteration.EndDate. However, when I am loading a collection using .getCollection('Predecessors'), the Iteration Name is loaded but the StartDate and EndDate are not populated. The app code writes to the console to examine the Iteration object.
<!DOCTYPE html>
<html>
<head>
<title>Rally</title>
<script type="text/javascript" src="/apps/2.0rc3/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function () {
Ext.define('CustomApp', {
extend: 'Rally.app.App',
launch: function() {
MyApp = this;
MyApp.globalContext = this.getContext().getDataContext();
MyApp.PredecessorSearch = [];
Ext.getBody().mask('Loading...' );
// Update the filter
var filter = Ext.create('Rally.data.QueryFilter', {
property: 'DirectChildrenCount',
operator: '=',
value: '0'
});
var filter2 = Ext.create('Rally.data.QueryFilter', {
property: 'ScheduleState',
operator: '!=',
value: 'Accepted'
});
filter = filter.and( filter2 );
Ext.create('Rally.data.WsapiDataStore', {
autoLoad: true,
limit: Infinity,
model: 'UserStory',
context: MyApp.globalContext,
fetch: ['Feature', 'Parent', 'Children',
'FormattedID', 'Name',
'ScheduleState', 'c_DevKanban',
'Release', 'Iteration', 'StartDate', 'EndDate',
'Blocked', 'BlockedReason',
'Predecessors',
'Owner','ObjectID' ],
filters: [ filter ],
sorters: [{
property: 'Rank',
direction: 'ASC'
}],
listeners: {
load: function (store, records) {
Ext.each(records, function(record) {
record.data.PredecessorData = [];
console.log( "Story " + record.data.FormattedID, record.data.Iteration );
// Look for predecessors
//console.log( record.data.Predecessors );
if ( record.data.Predecessors &&
record.data.Predecessors.Count > 0 ) {
MyApp._loadPredecessorsInStory( record );
}
// End of look for associated predecessors
});
var wait = function(condFunc, readyFunc, checkInterval) {
var checkFunc = function() {
if( condFunc() ) { readyFunc(); }
else { setTimeout(checkFunc, checkInterval); }
};
checkFunc();
};
MyApp.PredecessorSearch.push( 'END' );
wait(
function() {
return ( MyApp.PredecessorSearch[0] == 'END' );
},
function() {
Ext.getBody().unmask();
},
1000
);
}
}
});
},
_loadPredecessorsInStory: function ( userStory ) {
// Add this user story to the end of the list
MyApp.PredecessorSearch.push( userStory.data.FormattedID );
userStory.getCollection('Predecessors').load({
scope: this,
autoLoad: true,
limit: Infinity,
context: MyApp.globalContext,
fetch: ['Requirement',
'FormattedID', 'Name',
'State', 'ScheduleState', 'c_DevKanban',
'Release', 'Iteration', 'StartDate', 'EndDate',
'Blocked', 'BlockedReason',
'Owner','ObjectID' ],
filters: [ {
property: 'ScheduleState',
operator: '!=',
value: 'Accepted' }
],
callback: function(records, operation, success){
Ext.Array.each(records, function(record){
console.log( "Predecessor " + record.data.FormattedID, record.data.Iteration );
});
// Remove this user story from the front of the list
MyApp._removeElement( MyApp.PredecessorSearch, userStory.data.FormattedID );
}
});
},
_removeElement: function( array, element ) {
for(var i = array.length - 1; i >= 0; i--) {
if(array[i] === element) {
array.splice(i, 1);
return true;
}
}
return false;
}
});
Rally.launchApp('CustomApp', {
name:"Rally",
parentRepos:""
});
});
</script>
</head>
<body>
</body>
</html>
I seem to remember this being a bug in the 2.0rc3 version with the getCollection method. What if you pass your fetch in the config argument rather than passing it to load?
userStory.getCollection('Predecessors', {
fetch: ['Iteration', 'StartDate', 'EndDate', etc...]
}).load()...
You could also try upgrading to a more recent SDK version- 2.0 or the most recent 2.1...
I am confused as to whether you are expecting the "StartDate" and "EndDate" fields as defined in the fetch[] to be populated or whether you are expecting the Iteration.EndDate and Iteration.StartDate to be populated. The first ones don't exist on a user story (which is the type the getCollection is going to return) they only exist on the Iteration type.
I think KyleM was referring to the default fields that are fetched for a type unless you override the list with another list - which I a not sure how to do when the item being fetched is part of a getCollection (which would take the config you specify and not pass it to the fetch of the iteration data)

Rally How can I export all the test cases that were run for a test set to CSV

We want to take a number test cases from previous iterations / test sets and import them into a new test set. The problem is that we cannot export the test cases from a test set into CSV or any other data format as all we get is a printable report. We have also tried copy and paste the printable report into MS Excel but it does not give a data format.
Any suggestions / HTML forms that can be used.
It is possible to copy TestSets in Rally UI and that functionality allows re-using member Test Cases from Iteration to Iteration or from Release to Release. When you copy Test Sets they preserve the member Test Cases, including Test Steps. Rally recommends this method to handle testing. If the same tests are use repeatedly, copy the test sets that contain them into the new iteration directly in the Rally UI. This resets the results for that iteration and keeps you from duplicating the testcases.
Mark's Ruby tools provide an alternative which allows selective copying of member test cases - something that does not happen when a testset is copied in the UI.
Here is a javascript example using AppSDK2. You may certainly customize it further. The main point of this example is to illustrate how to update a collection using AppSDK2.
A user may select a "source" iteration from the iteration combobox, then a testset combobox is populated with testsets scheduled for this iteration. Next a user can select a destination iteration from the third combobox that limits iterations to current and future iterations, and create a new testset scheduled for the "destination" iteration. The testcases from the "source" testset are copied to the new testset.
Currently updating collections will not work if the app is run outside of Rally, but this limitation will be corrected soon. It will allow collection updates outside of rally using CORS as long as rab run command is used. The code below should work inside Rally now. The deployment html is available from this github repo.
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
comboboxConfig: {
fieldLabel: 'Select a source Iteration',
labelWidth: 150,
width: 350
},
onScopeChange: function() {
if (!this.down('#parentPanel')) {
this._panel = Ext.create('Ext.panel.Panel', {
layout: 'hbox',
itemId: 'parentPanel',
componentCls: 'panel',
items: [
{
xtype: 'container',
itemId: 'pickerContainer',
},
{
xtype: 'container',
itemId: 'iterationContainer',
}
]
});
this.add(this._panel);
}
if (this.down('#testSetComboxBox')) {
this.down('#testSetComboxBox').destroy();
}
var testSetComboxBox = Ext.create('Rally.ui.combobox.ComboBox',{
itemId: 'testSetComboxBox',
storeConfig: {
model: 'TestSet',
limit: Infinity,
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()]
},
fieldLabel: 'Select a TestSet',
listeners:{
ready: function(combobox){
if (combobox.getRecord()) {
this._onTestSetSelected(combobox.getRecord());
}
else{
console.log('selected iteration has no testsets');
}
},
select: function(combobox){
if (combobox.getRecord()) {
this._onTestSetSelected(combobox.getRecord());
}
},
scope: this
}
});
this.down('#pickerContainer').add(testSetComboxBox);
},
_onTestSetSelected:function(testset){
var id = testset.get('ObjectID');
this._name = testset.get('Name');
testset.self.load(id, {
fetch: ['Name','TestCases'],
callback: this._onSourceRecordRead,
scope: this
});
},
_onSourceRecordRead: function(record) {
var that = this;
that._testcases = [];
var testcaseStore = record.getCollection('TestCases',{fetch:['Name','FormattedID']});
testcaseStore.load({
callback: function() {
_.each(testcaseStore.getRange(), function(tc){
that._testcases.push(tc.data._ref);
});
console.log(that._testcases);
that._selectFutureIteration();
}
});
},
_selectFutureIteration: function(){
if (!this.down('#iterationComboxBox')) {
var iterationComboxBox = Ext.create('Rally.ui.combobox.ComboBox',{
itemId: 'iterationComboxBox',
storeConfig: {
model: 'Iteration',
limit: Infinity,
pageSize: 100,
autoLoad: true,
filters: [
{
property: 'StartDate',
operator: '>=',
value: (new Date()).toISOString()
}
]
},
fieldLabel: 'Select a destination Iteration',
labelWidth: 150,
listeners:{
ready: function(combobox){
if (combobox.getRecord()) {
this._onFutureIterationSelected(combobox.getRecord());
}
else{
console.log('no current or future iterations');
}
},
select: function(combobox){
if (combobox.getRecord()) {
this._onFutureIterationSelected(combobox.getRecord());
}
},
scope: this
}
});
this.down('#iterationContainer').add(iterationComboxBox);
}
},
_onFutureIterationSelected:function(iteration){
var that = this;
that._iteration = iteration.data._ref;
if (!this.down('#create')) {
var createButton = Ext.create('Ext.Container', {
items: [
{
xtype : 'rallybutton',
text : 'create a testset',
itemId: 'create',
handler: function() {
that._createTestSet();
}
}
]
});
this.add(createButton);
}
},
_createTestSet: function(){
var that = this;
console.log('create testset scheduled for ', that._iteration);
Rally.data.ModelFactory.getModel({
type: 'TestSet',
success: function(model) {
that._model = model;
var ts = Ext.create(model, {
Name: that._name + 'Copy',
Iteration: that._iteration
});
ts.save({
callback: function(result, operation) {
if(operation.wasSuccessful()) {
console.log(result.get('Name'), ' ', result.get('Iteration')._refObjectName);
that._readRecord(result);
}
else{
console.log("?");
}
}
});
}
});
},
_readRecord: function(result) {
var id = result.get('ObjectID');
this._model.load(id, {
fetch: ['Name','TestCases'],
callback: this._onRecordRead(result),
scope: this
});
},
_onRecordRead: function(record, operation) {
console.log('There are ', record.get('TestCases').Count, ' in ', record.get('Name') );
var that = this;
var testcaseStore = record.getCollection('TestCases');
testcaseStore.load({
callback: function() {
testcaseStore.add(that._testcases);
testcaseStore.sync({
callback: function() {
console.log('success');
}
});
}
});
}
});
There are a couple of Ruby scripts that might help you here:
https://github.com/markwilliams970/Rally-Test-Set-Export
https://github.com/markwilliams970/Rally-Add-TestCases-TestSet
These do require a working Ruby scripting environment and some familiarity with configuring/running scripts at the command line. However, working in combination, these should be enough to accomplish your goal.

Rally SDK 2 manually filter or specify rallycardboard columns displayed

I am trying to create a simple rallycardboard app that displays projects as columns with the project backlog stories as cards. Then allow the drag/drop of cards to set the project. Code is attached.
If I specify 'Project" as the attribute, the board contains columns for all projects in the workspace. I wish to limit the columns shown to either
Scoped parent and children, or
Code a list of project columns. I have tried the manipulate the columns, columnConfig, context settings, but nothing produces the desired results.
<!DOCTYPE html>
<html>
<head>
<title>CardBoard Example</title>
<script type="text/javascript" src="/apps/2.0rc2/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function() {
Ext.define('ProjBoard', {
extend: 'Rally.app.App',
launch: function() {
if (cardBoardConfig) {
cardBoardConfig.destroy();
}
var cardBoardConfig = {
xtype: 'rallycardboard',
types: ['User Story'],
attribute: 'Project',
fieldToDisplay: 'Project',
cardConfig: {
fields: ['Project', 'Parent','Iteration']
},
storeConfig: {
filters: [
{ property: 'ScheduleState', operator: '<', value: 'In-Progress' },
{ property: 'Iteration', operator: '=', value: '' }
],
sorters: [
{ property: 'Rank', direction: 'DESC' }
],
//Specify current project and scoping
context: this.getContext().getDataContext()
}
};
this.add(cardBoardConfig);
}
});
Rally.launchApp('ProjBoard', {
name: 'Backlog Project Board'
});
});
</script>
<style type="text/css">
</style>
</head>
<body></body>
</html>
You should be able to specify the columns via config:
https://help.rallydev.com/apps/2.0rc2/doc/#!/api/Rally.ui.cardboard.CardBoard-cfg-columns
columns: [
{
value: '/project/12345',
columnHeaderConfig: {
headerTpl: '{project}',
headerData: {project: 'Project 1'}
}
},
//more columns...
]
The code below allowed me to cut down a dozen of project columns to three. First I get current project and query a collection of its child projects to build an array of projects I want to have on the board (you may choose a different criteria for what projects you want on the board), and then I extended Rally.ui.cardboard.CardBoard to overwrite its _buildColumnsFromModel method where only columns that meet this condition are filtered in :
retrievedColumns = _.select(retrievedColumns, function(project){
return that.arrayOfProjectRefs.indexOf(project.value) != -1
});
Here is the full js file. Apart from those changes, this is your code.
Ext.define('CustomApp', { extend: 'Rally.app.App', componentCls: 'app',
launch: function() {
var that = this;
that.arrayOfProjectRefs = [];
var p = this.getContext().getProject();
Ext.create('Rally.data.wsapi.Store', {
model: 'Project',
fetch: ['Children'],
filters:[
{
Property: '_ref',
value: p
}
],
pageSize: 1,
autoLoad: true,
listeners: {
load: function(store, records) {
var project = records[0];
var childProjects = project.get('Children');
var childProjectsCount = project.get('Children').Count;
console.log('childProjectsCount', childProjectsCount);
that.arrayOfProjectRefs.push(project.get('_ref'));
project.getCollection('Children').load({
fetch: ['_ref', 'Name', 'State'],
callback: function(records, operation, success) {
Ext.Array.each(records, function(child) {
console.log(child.get('_ref') + ' - ' + child.get('Name') + child.get('State'));
if (child.get('State') === 'Open') {
that.arrayOfProjectRefs.push(child.get('_ref'));
--childProjectsCount;
if (childProjectsCount === 0) {
that._buildBoard();
}
}
});
}
});
}
}
});
},
_buildBoard:function(){
var that = this;
console.log('app._arrayOfProjectRefs', this.arrayOfProjectRefs);
Ext.define('ProjectCardboard', {extend: 'Rally.ui.cardboard.CardBoard',
xtype: 'projectCardboard',
_buildColumnsFromModel: function() {
var model = this.models[0];
if (model) {
var attribute = model.getField('Project');
if (attribute) {
attribute.getAllowedValueStore().load({
callback: function(records, operation, success) {
var retrievedColumns = _.map(records, function(allowedValue) {
var displayValue, value = allowedValue.get('StringValue');
if (!value && attribute.attributeDefinition.AttributeType.toLowerCase() === 'rating') {
value = "None";
} else if (attribute.attributeDefinition.AttributeType.toLowerCase() === 'object') {
displayValue = value;
value = allowedValue.get('_ref');
if (value === 'null') {
value = null;
}
}
return {
value: value,
columnHeaderConfig: {
headerTpl: displayValue || value || 'None'
}
};
});
this.fireEvent('columnsretrieved', this, retrievedColumns);
retrievedColumns = _.select(retrievedColumns, function(project){
return that.arrayOfProjectRefs.indexOf(project.value) != -1
});
console.log('retrievedColumns after filter', retrievedColumns)
this.columnDefinitions = [];
_.each(retrievedColumns, this.addColumn, this);
this.renderColumns();
},
scope: this
});
}
}
}
});
var addNewConfig = {
xtype: 'rallyaddnew',
recordTypes: ['User Story'],
ignoredRequiredFields: ['Name', 'Iteration'],
showAddWithDetails: false,
};
this.addNew = this.add(addNewConfig);
var myCardConfig = {
xtype: 'rallycard',
fields: ['ScheduleState','Name'],
maxHeight: 100
}
var cardBoardConfig = {
xtype: 'projectCardboard',
types: ['User Story'],
attribute: 'Project',
cardConfig: myCardConfig
};
this.cardBoard = this.add(cardBoardConfig);
}
});

Ext.ux.Image : Cannot read property 'dom' of undefined

I need a real <img> HTML tag in my view Sencha.
I've retrieved this code from the official doc :
Ext.define('Ext.ux.Image', {
extend: 'Ext.Component', // subclass Ext.Component
alias: 'widget.managedimage', // this component will have an xtype of 'managedimage'
autoEl: {
tag: 'img',
src: Ext.BLANK_IMAGE_URL,
cls: 'my-managed-image'
},
// Add custom processing to the onRender phase.
// Add a ‘load’ listener to the element.
onRender: function() {
this.autoEl = Ext.apply({}, this.initialConfig, this.autoEl);
this.callParent(arguments);
this.el.on('load', this.onLoad, this);
},
onLoad: function() {
this.fireEvent('load', this);
},
setSrc: function(src) {
if (this.rendered) {
this.el.dom.src = src;
} else {
this.src = src;
}
},
getSrc: function(src) {
return this.el.dom.src || this.src;
}
});
When i try to do setSrc, I get this error : Cannot read property 'dom' of undefined
Your code is from Ext.Js 4.x docs. You should use sencha touch 2 docs.
Please compare:
http://docs.sencha.com/ext-js/4-1/#!/api/Ext.Component
and
http://docs.sencha.com/touch/2-0/#!/api/Ext.Component
They are different.
As i understand you need real < img > tag in your view. If you use Ext.Img it will create a div container with background-image.
I know two ways:
set up tpl and data property.
Ext.create('Ext.Component', {
config: {
tpl: '',
data: {
url: 'http://example.com/pics/1.png',
imgClass: 'my-class'
}
}
});
set html config.
Ext.create('Ext.Component', {
config: {
html: ' <img class="my-class" src="http://example.com/pics/1.png">'
}
});

Dojo setQuery() on DataGrid - all items disappear?

I've racked my brain and done tons of research and testing and can't figure out what is going on.
I have a Dojo datagrid which is declared statically with some HTML. Using the GUI, my users will add items to the DataGrid, which works as it should. However, I'd like to have a function that is called at a certain point that uses Dojo's setQuery to filter the data that shows in the DataGrid. The problem is that once I run the setQuery command, ALL of the data in the grid disappears, no matter if it matches the query or not!
Here is some sample code:
var layoutItems = [[
{
field: "id",
name: "ID",
width: '5px',
hidden: true
},
{
field: "color",
name: "Color",
width: '80px'
}
]];
// Create an empty datastore //
var storeData = {
identifier: 'id',
label: 'id',
items: []
}
var store3 = new dojo.data.ItemFileWriteStore( {data : storeData} );
...
<div id="grid" dojoType="dojox.grid.DataGrid" jsId="grid5" store="store3" structure="layoutItems" queryOptions="{deep:true}" query="{}" rowsPerPage="40"></div>
...
function filterGrid() {
dijit.byId("grid").setQuery({color:"Red"});
}
....
function addItemToGrid(formdata) {
var jsonobj = eval("(" + dojo.toJson(formData, true) + ")");
var myNewItem = {
id: transactionItemID,
color: jsonobj.color
};
// Insert the new item into the store:
store3.newItem(myNewItem);
store3.save({onComplete: savecomplete, onError: saveerror});
}
Managed to fix it by running the a grid FILTER instead of setQuery periodically in the background with the help of some jQuery (not sure if setQuery would have worked as well, I don't really know the difference between the filter and setQuery, but filter is doing what I need it to do).
Here is some sample code; hope this helps someone else having problems with this:
// ADD JQUERY
<script src="http://code.jquery.com/jquery-latest.js"></script>
.
// PUT THIS IN THE <HEAD> OF THE PAGE
<script type="text/javascript">
$(document).ready(function() {
function filterTheDataGrid() {
if (dijit.byId("grid") != undefined) {
dijit.byId("grid").filter({color: "Red"});
}
}
// RUN THE filterTheDataGrid FUNCTION EVERY ONE SECOND (1000 MILLISECONDS) //
// LOWER '1000' FOR FASTER REFRESHING, MAYBE TO 500 FOR EVERY 0.5 SECOND REFRESHES //
var refreshDataGrid = setInterval(function() { filterTheDataGrid(); }, 1000);
}
</script>
.
// PUT THIS IN THE <HEAD> OF THE PAGE
<script type="text/javascript">
// SETUP THE LAYOUT FOR THE DATA //
var layoutItems = [[
{
field: "id",
name: "ID",
width: '5px',
hidden: true
},
{
field: "color",
name: "Color",
width: '80px'
}
]];
// Create an empty datastore //
var storeData = {
identifier: 'id',
label: 'id',
items: []
}
var store3 = new dojo.data.ItemFileWriteStore( {data : storeData} );
</script>
.
// PUT THIS IN THE <HTML> OF THE PAGE
<div id="grid" dojoType="dojox.grid.DataGrid" jsId="grid5" store="store3" structure="layoutItems" query="{ type: '*' }" clientSort="true" rowsPerPage="40"></div>
.
<script type="text/javascript">
function addItemToGrid(formdata) {
// THIS FUNCTION IS CALLED BY A DIALOG BOX AND GETS FORM DATA PASSED TO IT //
var jsonobj = eval("(" + dojo.toJson(formData, true) + ")");
var myNewItem = {
id: transactionItemID,
color: jsonobj.color
};
// Insert the new item into the store:
store3.newItem(myNewItem);
store3.save({onComplete: savecomplete, onError: saveerror});
}
</script>
Here is another option that I came up with, so that the filter is not running unnecessarily every x milliseconds; this basically uses JavaScript to make a new setInterval which runs once after 500 milliseconds and then does a clearInterval so that it doesn't run again. Looks like just calling the filterTheDataGrids() function after adding an item won't do.. we have to delay for a split second and then call it:
// PUT THIS IN THE <HEAD> OF THE PAGE
<script type="text/javascript">
// Declare the global variables
var refreshDataGrid;
var refreshDataGridInterval = 500; // Change this as necessary to control how long to wait before refreshing the Data Grids after an item is added or removed.
</script>
.
// PUT THIS IN THE <HEAD> OF THE PAGE
<script type="text/javascript">
function filterTheDataGrids() {
if (dijit.byId("grid") != undefined) {
dijit.byId("grid").filter({color: "Red"});
}
clearInterval (refreshDataGrid); // Running the filter just once should be fine; if the filter runs too quickly, then make the global refreshDataGridInterval variable larger
}
</script>
.
// PUT THIS IN THE <HEAD> OF THE PAGE
<script type="text/javascript">
// SETUP THE LAYOUT FOR THE DATA //
var layoutItems = [[
{
field: "id",
name: "ID",
width: '5px',
hidden: true
},
{
field: "color",
name: "Color",
width: '80px'
}
]];
// Create an empty datastore //
var storeData = {
identifier: 'id',
label: 'id',
items: []
}
var store3 = new dojo.data.ItemFileWriteStore( {data : storeData} );
</script>
.
// PUT THIS IN THE <HTML> OF THE PAGE
<div id="grid" dojoType="dojox.grid.DataGrid" jsId="grid5" store="store3" structure="layoutItems" query="{ type: '*' }" clientSort="true" rowsPerPage="40"></div>
.
<script type="text/javascript">
function addItemToGrid(formdata) {
// THIS FUNCTION IS CALLED BY A DIALOG BOX AND GETS FORM DATA PASSED TO IT //
var jsonobj = eval("(" + dojo.toJson(formData, true) + ")");
var myNewItem = {
id: transactionItemID,
color: jsonobj.color
};
// Insert the new item into the store:
store3.newItem(myNewItem);
store3.save({onComplete: savecomplete, onError: saveerror});
// Create setInterval on the filterTheDataGrids function; since simple calling the function won't do; seems to call it too fast or something
refreshDataGrid = setInterval(function() { filterTheDataGrids(); }, refreshDataGridInterval);
}
</script>