Suitescript 2.0-Why is a new line inserted into a sublist on record edit - suitescript2.0

I have the following client script deployed onto a vendor bill
/**
*#NApiVersion 2.x
*#NScriptType ClientScript
*/
define(["N/record"], function (record) {
/**
* #param {ClientScriptContext.fieldChanged} context
*/
function fieldChanged(context) {
//the if condition ensures the field changed trigger does not execute on changes in sublist. setting this to
//!context.sublistid ensures changes in department field and location field for the sublist do not trigger the script
if (
!context.sublistId &&
(context.fieldId == "department" || context.fieldId == "location")
) {
log.debug("sublistId", context.sublistId)
log.debug("fieldId", context.fieldId)
var recordObj = context.currentRecord
var departmentMain = recordObj.getValue({ fieldId: "department" })
var locationMain = recordObj.getValue({ fieldId: "location" })
var expenseSublist = recordObj.getSublist({ sublistId: "expense" })
var expenseSublistLength = recordObj.getLineCount({
sublistId: "expense",
})
log.debug("department", departmentMain)
log.debug("location", locationMain)
log.debug("expenseSublist.length", expenseSublistLength)
for (var i = 0; i < expenseSublistLength; i++) {
recordObj.selectLine({
sublistId: "expense",
line: i,
})
var departmentLine = recordObj.setCurrentSublistValue({
sublistId: "expense",
fieldId: "department",
value: departmentMain,
ignoreFieldChange: true,
})
var locationLine = recordObj.setCurrentSublistValue({
sublistId: "expense",
fieldId: "location",
value: locationMain,
ignoreFieldChange: true,
})
recordObj.commitLine({ sublistId: "expense" })
log.debug("department", departmentLine)
log.debug("location", locationLine)
}
}
}
/**
* #param {ClientScriptContext.postSourcing} context
*/
function postSourcing(context) {
var currentRecord = context.currentRecord
var sublistName = context.sublistId
var sublistFieldName = context.fieldId
var line = context.line
var departmentMain = currentRecord.getValue({ fieldId: "department" })
var locationMain = currentRecord.getValue({ fieldId: "location" })
if (sublistName === "expense" && sublistFieldName === "account") {
var expenseSublist = currentRecord.getSublist({ sublistId: "expense" })
var expenseSublistLength = currentRecord.getLineCount({
sublistId: "expense",
})
log.debug("department-main", departmentMain)
log.debug("location-main", locationMain)
currentRecord.setCurrentSublistValue({
sublistId: "expense",
fieldId: "department",
value: departmentMain,
ignoreFieldChange: false,
})
currentRecord.setCurrentSublistValue({
sublistId: "expense",
fieldId: "location",
value: locationMain,
ignoreFieldChange: false,
})
}
}
return {
fieldChanged: fieldChanged,
postSourcing: postSourcing,
}
})
When I edit an existing vendor bill record and attempt to save, I get the following prompt
It looks like placing the record into edit mode has also inserted a new blank line into the existing sublist despite no fields being clicked
Why would that be the case? Based on the above script, my expectation is that the postsourcing and field changed functions should only occur if the relevant fields (department and location for field changed and account for post sourcing) are clicked.
Is there something my script is missing?

Related

Add quantity for expense sublist on line commit

I have an expense report transaction where I am trying to sum the quantity field on the sublist and update a custom field (i.e. basically a running total that is updated every time a line is committed)
This is a part of the client script that I have so far:
function sublistChanged(context) {
var currentRecord = context.currentRecord;
var sublistName = context.sublistId;
var sublistFieldName = context.fieldId;
var op = context.operation;
totalExpQuantity = 0;
if (sublistName === "expense") {
for (var i = 0; i < sublistName.length; i++) {
var quantity = currentRecord.getCurrentSublistValue({
sublistId: "expense",
fieldId: "quantity",
});
log.debug("quantity", quantity);
}
totalExpQuantity += parseInt(quantity);
var lineCount = currentRecord.getLineCount({
sublistId: "expense",
});
console.log(lineCount);
currentRecord.setValue({
fieldId: "custbody_mileage_exp_report",
value:
"Total has changed to " +
currentRecord.getValue({
fieldId: "amount",
}) +
" with operation: " +
op +
" total quantity: " +
totalExpQuantity,
});
}
}
Every time a line is committed with a quantity value, I want the total to increment by the value of the line that was committed
i.e. here, after every line is committed, the total should go from 10,000, to 10,500 to 15,500
I have tried a variation of this code where the following line is part of the 'for loop':
totalExpQuantity += parseInt(quantity);
i.e.
for (var i = 0; i < sublistName.length; i++) {
var quantity = currentRecord.getCurrentSublistValue({
sublistId: "expense",
fieldId: "quantity",
});
totalExpQuantity += parseInt(quantity);
log.debug("quantity", quantity);
}
I get the following result:
Is what I am trying to do possible?
Where am I going wrong in the code? Should it be a different entry point?
I have also tried postsourcing though it didn't retrieve the quantity field and returned a blank
Found the answer. This is the working version of the script:
/**
*#NApiVersion 2.x
*#NScriptType ClientScript
*/
define(["N/search"], function (search) {
function fieldChanged(context) {
try {
var recordObj = context.currentRecord;
var employeeObj = parseInt(
recordObj.getValue({
fieldId: "entity",
})
);
if (context.fieldId == "entity") {
var employeeName = parseInt(employeeObj);
log.debug("employee", employeeName);
var expensereportSearchObj = search.create({
type: "expensereport",
filters: [
["type", "anyof", "ExpRept"],
"AND",
["expensecategory", "anyof", "8"],
"AND",
["expensedate", "within", "thisfiscalyear"],
"AND",
["employee", "anyof", employeeObj],
],
columns: [
search.createColumn({
name: "entityid",
join: "employee",
label: "Name",
}),
search.createColumn({ name: "tranid", label: "Document Number" }),
search.createColumn({
name: "expensecategory",
label: "Expense Category",
}),
search.createColumn({ name: "expensedate", label: "Expense Date" }),
search.createColumn({ name: "currency", label: "Currency" }),
search.createColumn({
name: "quantity",
join: "expenseDetail",
sort: search.Sort.ASC,
label: "Quantity",
}),
],
});
var searchResult = expensereportSearchObj
.run()
.getRange({ start: 0, end: 1000 });
log.debug("result", JSON.stringify(searchResult));
var searchResultCount = expensereportSearchObj.runPaged().count;
log.debug("expensereportSearchObj result count", searchResultCount);
let q = 0;
for (var i = 0; i < searchResult.length; i++) {
var quantity = searchResult[i].getValue(searchResult[i].columns[5]);
log.debug("quantity", quantity);
q += parseInt(quantity);
log.debug("q", q);
}
recordObj.setValue({
fieldId: "custbody_employee_mileage_ytd",
value: q,
});
}
//loop through all results add +- to 0. see video for sub;ists
} catch (error) {
log.debug(
error.name,
"recordObjId: " +
recordObj +
", employee:" +
employeeName +
", message: " +
error.message +
", cause: " +
error.cause
);
}
}
function sublistChanged(context) {
var recordObj = context.currentRecord;
var sublistName = recordObj.getSublist({ sublistId: "expense" });
var lineCount = recordObj.getLineCount({ sublistId: "expense" });
var totalQuantity = 0;
for (i = 0; i < lineCount; i++) {
var expenseCategory = recordObj.getSublistValue({
sublistId: "expense",
fieldId: "category",
line: i,
});
log.debug("expenseCategory", expenseCategory);
var expenseQuantity = recordObj.getSublistValue({
sublistId: "expense",
fieldId: "quantity",
line: i,
});
if (expenseCategory == 8) {
totalQuantity += expenseQuantity;
}
recordObj.setValue({
fieldId: "custbody_mileage_exp_report",
value: totalQuantity,
});
}
}
return {
fieldChanged: fieldChanged,
sublistChanged: sublistChanged,
};
});

How to write SuiteScript for map reduce CSV file or JSON data as an input process it and Create a customer Record

Map Reduce script for Csv file or JSON Data as input process it and create a customer record
Suite Answer 43795 has an excellent "Processing Invoices Example" sample script that you can work from, it is also pasted below. Suite Answer 43795, also has additional information for each map/reduce stage.
/**
* #NApiVersion 2.x
* #NScriptType MapReduceScript
*/
define(['N/search', 'N/record', 'N/email', 'N/runtime', 'N/error'],
function(search, record, email, runtime, error){
function handleErrorAndSendNotification(e, stage){
log.error('Stage: ' + stage + ' failed', e);
var author = -5;
var recipients = 'notify#xxxxxx.com';
var subject = 'Map/Reduce script ' + runtime.getCurrentScript().id + ' failed for stage: ' + stage;
var body = 'An error occurred with the following information:\n' + 'Error code: ' + e.name + '\n' + 'Error msg: ' + e.message;
email.send({
author: author,
recipients: recipients,
subject: subject,
body: body
});
}
function handleErrorIfAny(summary){
var inputSummary = summary.inputSummary;
var mapSummary = summary.mapSummary;
var reduceSummary = summary.reduceSummary;
if (inputSummary.error)
{
var e = error.create({
name: 'INPUT_STAGE_FAILED',
message: inputSummary.error
});
handleErrorAndSendNotification(e, 'getInputData');
}
handleErrorInStage('map', mapSummary);
handleErrorInStage('reduce', reduceSummary);
}
function handleErrorInStage(stage, summary){
var errorMsg = [];
summary.errors.iterator().each(function(key, value){
var msg = 'Failure to accept payment from customer id: ' + key + '. Error was: ' + JSON.parse(value).message + '\n';
errorMsg.push(msg);
return true;
});
if (errorMsg.length > 0)
{
var e = error.create({
name: 'RECORD_TRANSFORM_FAILED',
message: JSON.stringify(errorMsg)
});
handleErrorAndSendNotification(e, stage);
}
}
function createSummaryRecord(summary){
try{
var seconds = summary.seconds;
var usage = summary.usage;
var yields = summary.yields;
var rec = record.create({
type: 'customrecord_summary',
});
rec.setValue({
fieldId : 'name',
value: 'Summary for M/R script: ' + runtime.getCurrentScript().id
});
rec.setValue({
fieldId: 'custrecord_time',
value: seconds
});
rec.setValue({
fieldId: 'custrecord_usage',
value: usage
});
rec.setValue({
fieldId: 'custrecord_yields',
value: yields
});
rec.save();
} catch(e){
handleErrorAndSendNotification(e, 'summarize');
}
}
function applyLocationDiscountToInvoice(recordId){
var invoice = record.load({
type: record.Type.INVOICE,
id: recordId,
isDynamic: true
});
var location = invoice.getText({
fieldId: 'location'
});
var discount;
if (location === 'East Coast')
discount = 'Eight Percent';
else if (location === 'West Coast')
discount = 'Five Percent';
else if (location === 'United Kingdom')
discount = 'Nine Percent';
else
discount = '';
invoice.setText({
fieldId: 'discountitem',
text: discount,
ignoreFieldChange : false
});
log.debug(recordId + ' has been updated with location-based discount.');
invoice.save();
}
function getInputData(){
return search.create({
type: record.Type.INVOICE,
filters: [['status', search.Operator.IS, 'open']],
columns: ['entity'],
title: 'Open Invoice Search'
});
}
function map(context){
var searchResult = JSON.parse(context.value);
var invoiceId = searchResult.id;
var entityId = searchResult.values.entity.value;
applyLocationDiscountToInvoice(invoiceId);
context.write({
key: entityId,
value: invoiceId
});
}
function reduce(context){
var customerId = context.key;
var custPayment = record.transform({
fromType: record.Type.CUSTOMER,
fromId: customerId,
toType: record.Type.CUSTOMER_PAYMENT,
isDynamic: true
});
var lineCount = custPayment.getLineCount('apply');
for (var j = 0; j < lineCount; j++){
custPayment.selectLine({
sublistId: 'apply',
line: j
});
custPayment.setCurrentSublistValue({
sublistId: 'apply',
fieldId: 'apply',
value: true
});
}
var custPaymentId = custPayment.save();
context.write({
key: custPaymentId
});
}
function summarize(summary){
handleErrorIfAny(summary);
createSummaryRecord(summary);
}
return {
getInputData: getInputData,
map: map,
reduce: reduce,
summarize: summarize
};
});

How to join two collections in a json store?

I am working on IBM Worklight. I need to access data from two collections. Is there any solution for joining the 2 collections and get the required fields ?
JSONSTORE does not have the ability to combine collections.
However the follow blog post details one way to achieve this: https://mobilefirstplatform.ibmcloud.com/blog/2015/02/24/working-jsonstore-collections-join/
Create a collection:
var OrdersCollection = {
orders: {
searchFields: {
order_id: 'integer',
order_date: 'string'
},
additionalSearchFields: {
customer_id: 'integer'
}
}
};
var CustomerCollection = {
customers: {
searchFields: {
customer_name : 'string',
contact_name : 'string',
country : 'string'
},
additionalSearchFields : {
customer_id : 'integer'
}
}
};
Add data using additional search fields:
var data = [
{order_id : 462, order_date : '1-1-2000'},
{order_id: 608, order_date : '2-2-2001'},
{order_id: 898, order_date : '3-3-2002'}
];
var counter = 0;
var q = async.queue(function (task, callback) {
setTimeout(function () {
WL.JSONStore.get('orders').add(task.data, {additionalSearchFields: {customer_id: task.customer_id}});
callback(++counter);
},0);
},1);
for(var i = 0; i < data.length; i++){
q.push({data : data[i], customer_id: i+1}, function(counter){
console.log("Added Order Doc " + counter);
});
}
q.drain = function(){
setTimeout(function() {
console.log("Finished adding order documents");
WL.JSONStore.get("orders").findAll({filter : ["customer_id", "order_id", "order_date"]})
.then(function (res) {
ordersCollectionData = res;
document.getElementById("orders").innerHTML = JSON.stringify(res, null, "\t");
})
.fail(function (err) {
console.log("There was a problem at " + JSON.stringify(err));
});
},0);
};
Find the documents to merge:
WL.JSONStore.get('orders').findAll({filter : ['customer_id', 'order_id', 'order_date']})
.then(function (res) {
ordersCollectionData = res;
});
Merge
var shared_index = 'customer_id';
var mergedCollection = [];
mergedCollection =_.merge(customerCollectionData, ordersCollectionData, function(a, b){
if(_.isObject(a) && (a[shared_index] == b[shared_index])) {
return _.merge({}, a, b);
}
});

Ember CLI - Custom DS.LSAdapter.extend returns undefined

I want to save claims in localstorage with a custom LSAdapter.
The app/adapters folder has two adapters: application.js and claim.js. After login I want to save de retrieved claims in de store, like this:
claimarr.forEach(function(claimString){
var claimsplitted = claimString.split(" ");
var record = store.createRecord('claim',
{
'type' : claimsplitted[0],
'value': claimsplitted[1]
}
);
record.save();
});
I am not able to create the custom LSAdapter. I am using Simple Auth, and the addon writes data to localStorage!
adapters/application.js
import DS from "ember-data";
export default DS.RESTAdapter.reopen({
namespace: 'api/',
host: 'http://localhost:1336',
headers: function() {
return {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json"
};
}.property().volatile()
});
adapters/claim.js
import DS from "ember-data";
export default DS.LSAdapter.extend({
namespace: 'api/'
}
);
Brocfile.js
..
app.import("bower_components/ember-localstorage-adapter/localstorage_adapter.js");
module.exports = app.toTree();
models/claim.js
import DS from "ember-data";
export default DS.Model.extend({
type : DS.attr('string'),
value : DS.attr('string'),
});
UPDATE
Workaround in my logincontroller:
controllers/login.js
actions: {
login: function(){
//this.set('currentDate', moment());
//var now = moment;
//console.log(this.now);
var self = this;
self.set('errorMessage', null);
this.get('session').authenticate(this.authenticator, {
identification: this.get('identification'),
password: this.get('password')
})
.then(
function() {
function getClaimSet(session){
var claimSet = [];
//build claimSet from base64 string
var claimarr = window.atob(session.content.claims).split(",");
claimarr.forEach(function(claimString){
var claimsplitted = claimString.split(" ");
var record =
{
'type' : claimsplitted[0],
'value': claimsplitted[1]
};
claimSet.push(record);
});
return claimSet;
}
var session = self.get('session');
var claimSet = getClaimSet(session);
session.set("claimSet" , claimSet );
session["hasClaim"] = function(value, type){
if(typeof(type) === "undefined"){
type = "Method";
}
var foundArr = session.content.claimSet.filter(function(claim){return claim.type === type && claim.value === value;});
return foundArr.length > 0;
};
I'm now able to use: session.hasClaim(value, type)

Rally Kanban # of days since first state?

Is there a way to calculate the number of days since the card has been in the first state? Lets use say I use a custom field \for the kanban states. 1,2,3,4 If a card is in state 3 then how long has it been since # 1?
I am not sure of a way to automate it or flag items but if you review the US/DE in question just take a quick look at the revision history.
Any changes in state should be logged in the history.
Use Lookback API to access historic data.
Lookback API allows to see what any work item or collection of work items looked like in the past. This is different from using WS API directly, which can provide you with the current state of objects, but does not have historical data.
LBAPI documentation is available here
Here is an example that builds a grid of stories with a Kanban state. When a story's row is double-clicked, the time this story has spent in three Kanban states is calculated and a grid is built to show the values:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function(){
var x = Ext.create('Rally.data.lookback.SnapshotStore', {
fetch : ['Name','c_Kanban','_UnformattedID', '_TypeHierarchy'],
filters : [{
property : '__At',
value : 'current'
},
{
property : '_TypeHierarchy',
value : 'HierarchicalRequirement'
},
{
property : '_ProjectHierarchy',
value : 22222
},
{
property : 'c_Kanban', //get stories with Kanban state
operator : 'exists',
value : true
}
],
hydrate: ['_TypeHierarchy', 'c_Kanban'],
listeners: {
load: this.onStoriesLoaded,
scope: this
}
}).load({
params : {
compress : true,
removeUnauthorizedSnapshots : true
}
});
},
//make grid of stories with Kanban state
onStoriesLoaded: function(store, data){
var that = this;
var stories = [];
var id;
Ext.Array.each(data, function(record) {
var artifactType = record.get('_TypeHierarchy');
if (artifactType[artifactType.length - 1] == "HierarchicalRequirement") {
id = 'US' + record.get('_UnformattedID');
} else if (artifactType[artifactType.length - 1] == "Defect") {
id = 'DE' + record.get('_UnformattedID');
}
stories.push({
Name: record.get('Name'),
FormattedID: id,
UnformattedID: record.get('_UnformattedID'),
c_Kanban: record.get('c_Kanban')
});
console.log(stories);
});
var myStore = Ext.create('Rally.data.custom.Store', {
data: stories
});
if (!this.down('#allStoriesGrid')) {
this.add({
xtype: 'rallygrid',
id: 'allStoriesGrid',
store: myStore,
width: 500,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID',
},
{
text: 'Name', dataIndex: 'Name', flex: 1,
},
{
text: 'Kanban', dataIndex: 'c_Kanban'
}
],
listeners: {
celldblclick: function( grid, td, cellIndex, record, tr, rowIndex){
id = grid.getStore().getAt(rowIndex).get('UnformattedID');
console.log('id', id);
that.getStoryModel(id); //to eventually build a grid of Kanban allowed values
}
}
});
}else{
this.down('#allStoriesGrid').reconfigure(myStore);
}
},
getStoryModel:function(id){
console.log('get story model');
var that = this;
this.arr=[];
//get a model of user story
Rally.data.ModelFactory.getModel({
type: 'User Story',
context: {
workspace: '/workspace/11111',
project: 'project/22222'
},
success: function(model){
//Get store instance for the allowed values
var allowedValuesStore = model.getField('c_Kanban').getAllowedValueStore( );
that.getDropdownValues(allowedValuesStore, id);
}
});
},
getDropdownValues:function(allowedValuesStore, id){
var that = this;
//load data into the store
allowedValuesStore.load({
scope: this,
callback: function(records, operation, success){
_.each(records, function(val){
//AllowedAttributeValue object in WS API has StringValue
var v = val.get('StringValue');
that.arr.push(v);
});
console.log('arr', this.arr);
that.getStoryById(id); //former makeStore
}
});
},
getStoryById:function(id){
var that = this;
var snapStore = Ext.create('Rally.data.lookback.SnapshotStore', {
fetch: ['c_Kanban', 'Blocked'],
hydrate:['c_Kanban','Blocked'],
filters : [
{
property : '_UnformattedID',
value : id //15
}
],
sorters:[
{
property : '_ValidTo',
direction : 'ASC'
}
]
});
snapStore.load({
params: {
compress: true,
removeUnauthorizedSnapshots : true
},
callback : function(records, operation, success) {
that.onDataLoaded(records, id);
}
});
},
onDataLoaded:function(records, id){
var times = [];
var measure = 'second';
//-----------------------backlog
var backlog = _.filter(records, function(record) {
return record.get('c_Kanban') === 'backlog';
});
console.log('backlog',backlog);
var cycleTimeFromBacklogToInProgress = '';
if (_.size(backlog) > 0) {
var backlog1 = _.first(backlog);
var backlog2 = _.last(backlog);
var backlogDate1 = new Date(backlog1.get('_ValidFrom'));
if (backlog2.get('_ValidTo') === "9999-01-01T00:00:00.000Z") { //infinity
backlogDate2 = new Date(); //now
}
else{
var backlogDate2 = new Date(backlog2.get('_ValidTo'));
}
cycleTimeFromBacklogToInProgress = Rally.util.DateTime.getDifference(backlogDate2,backlogDate1, measure );
}
times.push(cycleTimeFromBacklogToInProgress);
//console.log(cycleTimeFromBacklogToInProgress);
//----------------------in progress
var inProgress = _.filter(records, function(record) {
return record.get('c_Kanban') === 'in-progress';
});
console.log('in-progress',inProgress);
var cycleTimeFromInProgressToDone = '';
if (_.size(inProgress) > 0) {
var inProgress1 = _.first(inProgress);
var inProgress2 = _.last(inProgress);
var inProgressDate1 = new Date(inProgress1.get('_ValidFrom'));
if (inProgress2.get('_ValidTo') === "9999-01-01T00:00:00.000Z") { //infinity
inProgressDate2 = new Date(); //now
}
else{
var inProgressDate2 = new Date(inProgress2.get('_ValidTo'));
}
cycleTimeFromInProgressToDone = Rally.util.DateTime.getDifference(inProgressDate2,inProgressDate1, measure );
}
times.push(cycleTimeFromInProgressToDone);
//console.log(cycleTimeFromInProgressToDone);
//------------------------done
var done = _.filter(records, function(record) {
return record.get('c_Kanban') === 'done';
});
console.log('done',done);
var cycleTimeFromDoneToReleased = '';
if (_.size(done) > 0) {
var done1 = _.first(done);
var done2 = _.last(done);
var doneDate1 = new Date(done1.get('_ValidFrom'));
if (done2.get('_ValidTo') === "9999-01-01T00:00:00.000Z") { //infinity
doneDate2 = new Date(); //now
}
else{
var doneDate2 = new Date(done2.get('_ValidTo'));
}
cycleTimeFromDoneToReleased = Rally.util.DateTime.getDifference(doneDate2,doneDate1, measure );
}
times.push(cycleTimeFromDoneToReleased);
//console.log(cycleTimeFromDoneToReleased);
/**********
skip first '' element of the this.arr and last 'released' element of this.arr because
do not care for cycle times in first and last kanban states
Originally: arr ["", "backlog", "in-progress", "done", "released"] ,shorten to: ["backlog", "in-progress", "done"]
**********/
this.arrShortened = _.without(this.arr, _.first(this.arr),_.last(this.arr)) ;
console.log('this.arrShortened with first and last skipped', this.arrShortened); //["backlog", "in-progress", "done"]
cycleTimes = _.zip(this.arrShortened, times);
//console.log('cycleTimes as multi-dimentional array', cycleTimes);
cycleTimes = _.object(cycleTimes);
//console.log('cycleTimes as object', cycleTimes); //cycleTimes as object Object {backlog: 89, in-progress: 237, done: 55}
var cycleTimesArray = [];
cycleTimesArray.push(cycleTimes);
console.log('cycleTimesArray',cycleTimesArray);
var store = Ext.create('Rally.data.custom.Store',{
data: cycleTimesArray,
pageSize: 100
});
var columnConfig = [];
_.each(cycleTimes,function(c,key){
var columnConfigElement = _.object(['text', 'dataIndex', 'flex'], ['time spent in ' + key, key, 1]);
columnConfig.push(columnConfigElement);
});
var title = 'Kanban cycle time for US' + id + ' in ' + measure + 's'
if (!this.grid) {
this.grid = this.add({
xtype: 'rallygrid',
title: title,
width: 500,
itemId: 'grid2',
store: store,
columnCfgs: columnConfig
});
}
else{
this.down('#grid2').reconfigure(store);
}
}
});