Definition for rule 'vue/require-prop-types' was not found vue/require-prop-types - vue.js

I'm currently struggling with this issue when trying to build the administration on my shopware6 instance.
I am not sure to understand what it's expecting with this error
Definition for rule 'vue/require-prop-types' was not found vue/require-prop-types
This is pointing the line 22 which is :
props: {
Here's the index.js file :
import './custom-entity-single-select.scss';
import template from './custom-entity-single-select.html.twig';
const { Component, Mixin, Utils } = Shopware;
const { Criteria, EntityCollection } = Shopware.Data;
const { debounce, get } = Shopware.Utils;
Component.register('custom-entity-single-select', {
template,
inject: { repositoryFactory: 'repositoryFactory', feature: 'feature' },
mixins: [
Mixin.getByName('remove-api-error'),
],
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
required: false,
},
highlightSearchTerm: {
type: Boolean,
required: false,
default: true,
},
placeholder: {
type: String,
required: false,
default: '',
},
resetOption: {
type: String,
required: false,
default: '',
},
labelProperty: {
type: [String, Array],
required: false,
default: 'name',
},
labelCallback: {
type: Function,
required: false,
default: null,
},
entity: {
required: true,
type: String,
},
resultLimit: {
type: Number,
required: false,
default: 25,
},
criteria: {
type: Object,
required: false,
default() {
return (new Criteria(1, this.resultLimit)).getAssociation('stateMachine').addFilter(Criteria.equals('state_machine_state.stateMachine.technicalName', 'order_transaction.state'));
},
},
context: {
type: Object,
required: false,
default() {
return Shopware.Context.api;
},
},
disableAutoClose: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
searchTerm: '',
isExpanded: false,
resultCollection: null,
singleSelection: null,
isLoading: false,
// used to track if an item was selected before closing the result list
itemRecentlySelected: false,
lastSelection: null,
};
},
computed: {
inputClasses() {
return {
'is--expanded': this.isExpanded,
};
},
selectionTextClasses() {
return {
'is--placeholder': !this.singleSelection,
};
},
repository() {
return this.repositoryFactory.create(this.entity);
},
/**
* #returns {EntityCollection}
*/
results() {
return this.resultCollection;
},
},
watch: {
value(value) {
// No need to fetch again when the new value is the last one we selected
if (this.lastSelection && this.value === this.lastSelection.id) {
this.singleSelection = this.lastSelection;
this.lastSelection = null;
return;
}
if (value === '' || value === null) {
this.singleSelection = null;
return;
}
this.loadSelected();
},
},
created() {
this.createdComponent();
},
methods: {
createdComponent() {
this.loadSelected();
},
/**
* Fetches the selected entity from the server
*/
loadSelected() {
if (!this.value) {
if (this.resetOption) {
this.singleSelection = {
id: null,
name: this.resetOption,
};
}
return Promise.resolve();
}
this.isLoading = true;
return this.repository.get(this.value, { ...this.context, inheritance: true }, this.criteria).then((item) => {
this.criteria.setIds([]);
this.singleSelection = item;
this.isLoading = false;
return item;
});
},
createCollection(collection) {
return new EntityCollection(collection.source, collection.entity, collection.criteria);
},
isSelected(item) {
return item.id === this.value;
},
debouncedSearch: debounce(function updateSearchTerm() {
this.search();
}, 400),
search() {
if (this.criteria.term === this.searchTerm) {
return Promise.resolve();
}
this.criteria.setPage(1);
this.criteria.setLimit(this.resultLimit);
this.criteria.setTerm(this.searchTerm);
this.resultCollection = null;
const searchPromise = this.loadData().then(() => {
this.resetActiveItem();
});
this.$emit('search', searchPromise);
return searchPromise;
},
paginate() {
if (!this.resultCollection || this.resultCollection.total < this.criteria.page * this.criteria.limit) {
return;
}
this.criteria.setPage(this.criteria.page + 1);
this.loadData();
},
loadData() {
this.isLoading = true;
return this.repository.search(this.criteria, { ...this.context, inheritance: true }).then((result) => {
this.displaySearch(result);
this.isLoading = false;
return result;
});
},
displaySearch(result) {
if (!this.resultCollection) {
this.resultCollection = result;
} else {
result.forEach(item => {
// Prevent duplicate entries
if (!this.resultCollection.has(item.id)) {
this.resultCollection.push(item);
}
});
}
if (this.resetOption) {
if (!this.resultCollection.has(null)) {
this.resultCollection.unshift({
id: null,
name: this.resetOption,
});
}
}
},
displayLabelProperty(item) {
if (typeof this.labelCallback === 'function') {
return this.labelCallback(item);
}
const labelProperties = [];
if (Array.isArray(this.labelProperty)) {
labelProperties.push(...this.labelProperty);
} else {
labelProperties.push(this.labelProperty);
}
return labelProperties.map(labelProperty => {
return this.getKey(item, labelProperty) || this.getKey(item, `translated.${labelProperty}`);
}).join(' ');
},
onSelectExpanded() {
this.isExpanded = true;
// Always start with a fresh list when opening the result list
this.criteria.setPage(1);
this.criteria.setLimit(this.resultLimit);
this.criteria.setTerm('');
this.resultCollection = null;
this.loadData().then(() => {
this.resetActiveItem();
});
// Get the search text of the selected item as prefilled value
this.searchTerm = this.tryGetSearchText(this.singleSelection);
this.$nextTick(() => {
this.$refs.customSelectInput.select();
this.$refs.customSelectInput.focus();
});
},
tryGetSearchText(option) {
if (typeof this.labelCallback === 'function') {
return this.labelCallback(option);
}
let searchText = this.getKey(option, this.labelProperty, '');
if (!searchText) {
searchText = this.getKey(option, `translated.${this.labelProperty}`, '');
}
return searchText;
},
onSelectCollapsed() {
// Empty the selection if the search term is empty
if (this.searchTerm === '' && !this.itemRecentlySelected) {
this.clearSelection();
}
this.$refs.customSelectInput.blur();
this.searchTerm = '';
this.itemRecentlySelected = false;
this.isExpanded = false;
},
closeResultList() {
this.$refs.selectBase.collapse();
},
setValue(item) {
this.itemRecentlySelected = true;
if (!this.disableAutoClose) {
this.closeResultList();
}
// This is a little against v-model. But so we dont need to load the selected item on every selection
// from the server
this.lastSelection = item;
this.$emit('change', item.id, item);
this.$emit('option-select', Utils.string.camelCase(this.entity), item);
},
clearSelection() {
this.$emit('before-selection-clear', this.singleSelection, this.value);
this.$emit('change', null);
this.$emit('option-select', Utils.string.camelCase(this.entity), null);
},
resetActiveItem(pos = 0) {
// Return if the result list is closed before the search request returns
if (!this.$refs.resultsList) {
return;
}
// If an item is selected the second entry is the first search result
if (this.singleSelection) {
pos = 1;
}
this.$refs.resultsList.setActiveItemIndex(pos);
},
onInputSearchTerm(event) {
const value = event.target.value;
this.$emit('search-term-change', value);
this.debouncedSearch();
},
getKey(object, keyPath, defaultValue) {
return get(object, keyPath, defaultValue);
},
},
});
And here you can find the whole gist containing this index.js, the html.twig file and the scss : https://gist.github.com/Youmar0504/2154bd1d16866d14644aa2a3a6fd513f
ALREADY TRIED
value: {
type: String,
required: false,
default: '',
},
value: {
type: Array,
required: false,
default: [],
},

I would assume you have an extra closing '}' at the value prop.
props: {
value: {
required: false,
} <--
},
highlightSearchTerm: {
type: Boolean,
required: false,
default: true,
},
placeholder: {
type: String,
required: false,
default: '',
},
resetOption: {
type: String,
required: false,
default: '',
},
labelProperty: {
type: [String, Array],
required: false,
default: 'name',
},
labelCallback: {
type: Function,
required: false,
default: null,
},
entity: {
required: true,
type: String,
},
resultLimit: {
type: Number,
required: false,
default: 25,
},
criteria: {
type: Object,
required: false,
default() {
return (new Criteria(1, this.resultLimit)).getAssociation('stateMachine').addFilter(Criteria.equals('state_machine_state.stateMachine.technicalName', 'order_transaction.state'));
},
},
context: {
type: Object,
required: false,
default() {
return Shopware.Context.api;
},
},
disableAutoClose: {
type: Boolean,
required: false,
default: false,
},
},

Related

How do I seperate an array so the data shows up as individual list items?

I am struggling re-writing some older code I made in vue. I have a form which allows the user to enter multiple email recipients. The problem is that the array in which these emails are stored outputs them as one single item.
How do I seperate these email addresses?
This is my code. I know that Vue has something called Split, but I am not sure where I could use it here (or if it would change anything).
<template>
<b-form-group :label="label">
<vue-tags-input
v-model="inputValue"
:tags="tags"
:autocomplete-min-length="0"
:add-on-key="[',', 13]"
:autocomplete-items="filteredAutocompleteItems"
:placeholder="$t('addEmailAddressesHere')"
data-test-id="send-doc-email-to"
#before-adding-tag="addTag"
#tags-changed="tagsChanged"
class="mw-100"
/>
</b-form-group>
</template>
<script>
import {propertyService} from '#/services/property';
import {tenancyService} from '#/services/tenancy';
import {unitService} from '#/services/unit';
import {userService} from '#/services/user';
import VueTagsInput from '#johmun/vue-tags-input';
export default {
components: {
VueTagsInput
},
props: {
value: Array,
label: String,
entityId: String,
entityType: String,
prefill: {
type: Boolean,
default: false
},
asNotification: {
type: Boolean,
default: false
},
includeUser: {
type: Boolean,
default: false
}
},
data() {
return {
inputValue: '',
autocompleteItems: []
};
},
computed: {
tags() {
return (this.value || []).map(this.setText);
},
filteredAutocompleteItems() {
return this.autocompleteItems.filter(autocompleteItem =>
autocompleteItem.text.toUpperCase().includes(this.inputValue.toUpperCase()));
}
},
methods: {
addTag({tag, addTag}) {
if (!tag.recipients) {
tag.recipients = [{emailAddress: tag.text}];
}
addTag(tag);
},
setText(tag) {
tag.text = [tag.description, tag.recipients.map(recipient => recipient.emailAddress).join(', ')].filter(Boolean).join(' | ');
return tag;
},
tagsChanged(newTags) {
this.$emit('input', newTags);
},
load() {
switch (this.entityType) {
case 'TENANCY':
userService.getCurrentUser().then(userResult => {
tenancyService.getTenants(this.entityId).then(result => {
const defaultTags = [];
const recipients = result.data
.map(tenant => tenant.legalEntity)
.filter(legalEntity => legalEntity.email || (!legalEntity.email && this.asNotification ? legalEntity.name : null))
.map(legalEntity => ({
emailAddress: legalEntity.email || (!legalEntity.email && this.asNotification ? legalEntity.name.concat(' ', `(${this.$t('letterMail').toLowerCase()})`) : null),
legalEntityId: legalEntity.id
}));
if (recipients.length) {
defaultTags.push(this.setText({description: this.$t('tenants'), recipients}));
}
this.autocompleteItems.push(...defaultTags);
if (this.includeUser) {
defaultTags.push(this.setText({
description: this.$t('user'),
recipients: [{emailAddress: userResult.data.email}]
}));
}
if (this.prefill) {
this.tagsChanged(defaultTags);
}
tenancyService.getUnits(this.entityId).then(result =>
result.data.forEach(unitTenancy => this.addPropertyContactsToAutocompleteItems(unitTenancy.unit.propertyId)));
});
});
break;
case 'UNIT':
unitService.get(this.entityId).then(result =>
this.addPropertyContactsToAutocompleteItems(result.data.propertyId));
break;
case 'PROPERTY':
this.addPropertyContactsToAutocompleteItems(this.entityId);
break;
}
},
addPropertyContactsToAutocompleteItems(propertyId) {
propertyService.listContacts(propertyId).then(result => {
this.autocompleteItems.push(...result.data
.filter(contact => contact.email)
.map(contact => this.setText({
description: contact.profession ? this.$t(`model.contact.professions.${contact.profession}`) : null,
recipients: [{emailAddress: contact.email, legalEntityId: contact.id}]
}))
);
});
}
},
created() {
this.load();
}
};
</script>

vuejs store localStorage set object

I have a theme config store, and I set "localStorage" on this store, I can read it the same way. but I'm recording them one by one, is it possible to send them all as objects instead, if possible, please give an example.
how do i set it on the store
I'm a little newbie in storage, please comment accordingly.
export const $themeConfig = {
app: {
appName: 'Name',
appLogoImage: '',
},
layout: {
skin: 'light',
type: 'vertical',
contentWidth: 'full',
menu: {
hidden: false,
isCollapsed: false,
},
navbar: {
type: 'floating',
backgroundColor: 'dark',
},
footer: {
type: 'static',
},
customizer: true,
},
}
import {$themeConfig} from '../../themeConfig'
export default {
namespaced: true,
state: {
layout: {
skin: localStorage.getItem('themeConfig-skin') || $themeConfig.layout.skin,
type: localStorage.getItem('themeConfig-menuLayout') || $themeConfig.layout.type,
contentWidth: localStorage.getItem('themeConfig-contentWidth') || $themeConfig.layout.contentWidth,
menu: {
hidden: localStorage.getItem('themeConfig-menuHidden') || $themeConfig.layout.menu.hidden,
},
navbar: {
type: localStorage.getItem('themeConfig-navbarType') || $themeConfig.layout.navbar.type,
backgroundColor: localStorage.getItem('themeConfig-navbarBG') || $themeConfig.layout.navbar.backgroundColor,
},
footer: {
type: localStorage.getItem('themeConfig-footerType') || $themeConfig.layout.footer.type,
},
},
},
getters: {},
mutations: {
UPDATE_SKIN(state, skin) {
state.layout.skin = skin
localStorage.setItem('themeConfig-skin', skin)
if (skin === 'dark') document.body.classList.add('dark-layout')
else if (document.body.className.match('dark-layout')) document.body.classList.remove('dark-layout')
},
UPDATE_LAYOUT_TYPE(state, val) {
localStorage.setItem('themeConfig-menuLayout', val)
state.layout.type = val
},
UPDATE_CONTENT_WIDTH(state, val) {
localStorage.setItem('themeConfig-contentWidth', val)
state.layout.contentWidth = val
},
UPDATE_NAV_MENU_HIDDEN(state, val) {
localStorage.setItem('themeConfig-menuHidden', val)
state.layout.menu.hidden = val
},
UPDATE_NAVBAR_CONFIG(state, obj) {
if (obj.backgroundColor)
localStorage.setItem('themeConfig-navbarBG', obj.backgroundColor)
else
localStorage.setItem('themeConfig-navbarType', obj.type)
Object.assign(state.layout.navbar, obj)
},
UPDATE_FOOTER_CONFIG(state, obj) {
if (obj.type)
localStorage.setItem('themeConfig-footerType', obj.type)
else
localStorage.setItem('themeConfig-footerType', obj.type)
Object.assign(state.layout.footer, obj)
},
},
actions: {},
}
You can create helper functions like:
function updateThemeConfig (key, data) {
let state = localStorage.getItem('themeConfig');
if(state){
state = JSON.parse(state);
state[key] = data;
} else {
state = {
[key]: data;
}
}
localStorage.setItem('themeConfig', JSON.stringify(state))
}
function getThemeConfig (key) {
let state = localStorage.getItem('themeConfig');
if(state) {
state = JSON.parse(state);
return state[key];
}
return;
}
Then use it like this:
footer: {
type: getThemeConfig('footerType') || $themeConfig.layout.footer.type,
},
...
UPDATE_LAYOUT_TYPE(state, val) {
updateThemeConfig('menuLayout', val);
state.layout.type = val
},

Getter undefined vuex and axios

I’m trying to get a getter in my component but it says an error. This is my code store.js
const store = new Vuex.Store({
state: {
config:{
themes: [],
typographies:[],
},
user: {
typography_id: 1,
theme_id: null
}
},
mutations: {
FETCH_CONFIG(state, config) {
state.config.themes = config.themes;
state.config.typographies = config.typographies;
},
FETCH_USER(state, user) {
state.user.theme_id = user.theme_id;
state.user.typography_id = user.typography_id;
},
},
actions: {
fetchConfig({commit}) {
axios.get('/api/config').then( function( response ){
commit('FETCH_CONFIG', response.data);
});
},
fetchUser({commit}) {
axios.get('/api/user').then( function( response ){
commit('FETCH_USER', response.data.data[0]);
});
},
},
getters: {
themes(state) {
return state.config.themes;
},
typographies(state) {
return state.config.typographies;
},
typography(state) {
if (state.user.theme_id == 1) {
return state.user.typography_id;
} else {
var theme = state.config.themes.filter(function (el) {
return el.id == state.user.theme_id;
});
return theme[0].typography_id;
}
},
user_theme(state) {
return state.user.theme_id;
},
}
});
And in my component in computed I have:
...mapGetters(['typographies', 'typography'])
And ths is the error I get:
I guess I’m doing something wrong but I don’t know what.
Your getter for typography returns the error because first it goes into the else and then tries to return theme[0].typography_id - but there is an empty array.. if you are loading the date, later on, make sure that the getter returns null before data is loaded.. like:
const store = new Vuex.Store({
state: {
config:{
themes: [],
typographies:[],
},
user: {
typography_id: 1,
theme_id: null
}
},
mutations: {
FETCH_CONFIG(state, config) {
state.config.themes = config.themes;
state.config.typographies = config.typographies;
},
FETCH_USER(state, user) {
state.user.theme_id = user.theme_id;
state.user.typography_id = user.typography_id;
},
},
actions: {
fetchConfig({commit}) {
axios.get('/api/config').then( function( response ){
commit('FETCH_CONFIG', response.data);
});
},
fetchUser({commit}) {
axios.get('/api/user').then( function( response ){
commit('FETCH_USER', response.data.data[0]);
});
},
},
getters: {
themes(state) {
return state.config.themes;
},
typographies(state) {
return state.config.typographies;
},
typography(state) {
if (state.user.theme_id == 1) {
return state.user.typography_id;
} else {
var theme = state.config.themes.filter(function (el) {
return el.id == state.user.theme_id;
});
return theme.length > 0 ? theme[0].typography_id: 1;
}
},
user_theme(state) {
return state.user.theme_id;
},
}
});

Reset Vue Bootstrap Table

i am using vue-bootstrap4-table in my application i have a custom input though which i search and populate the data,now i need to build a feature in which their is a cross button inside the search field and on clicking on it it should reset the table to empty state here is my code
<template>
<auto-complete
#autocomplete-result-selected="setCustomer"
placeholder="Enter Customer name"
:selected="selectedCustomer"
:styles="{width: 'calc(100% - 10px)'}"
index="locations"
attribute="name"
>
<template slot-scope="{ hit }">
<span>
{{ hit.customer.company && hit.customer.company + ' - ' }}{{ hit.customer.fname }}
{{ hit.customer.lname }}
</span>
</template>
</auto-complete>
<i class="fas fa-times" #click="clearTable()" v-show="selectedCustomer"></i>
</div>
</div>
</template>
<script>
import http from "../../helpers/api.js";
import AutoComplete from "../autocomplete/Autocomplete";
import axios from "axios";
import VueBootstrap4Table from "vue-bootstrap4-table";
export default {
components: {
"auto-complete": AutoComplete,
VueBootstrap4Table
},
computed: {},
data() {
return {
value: "",
selectedCustomer: "",
selectedFirstName: "",
selectedLastName: "",
selectedFields: [
{ name: "Invoice", value: "invoices" },
{
name: "Estimate",
value: "workorder_estimates"
}
],
filters: [
{ is_checked: true, value: "invoices", name: "Invoice" },
{ is_checked: true, value: "workorder_estimates", name: "Estimate" }
],
selectedFilters: [],
estimateChecked: false,
invoiceChecked: false,
config: {
card_mode: false,
show_refresh_button: false,
show_reset_button: false,
global_search: {
placeholder: "Enter custom Search text",
visibility: false,
case_sensitive: false
},
pagination: true,
pagination_info: false,
per_page: 10,
rows_selectable: true,
server_mode: true,
preservePageOnDataChange: true,
selected_rows_info:true
},
classes: {},
rows: [],
columns: [
{
label: "TYPE",
name: "type"
},
{
label: "ID",
name: "distinction_id"
},
{
label: "CUSTOMER NAME",
name: "full_name"
},
{
label: "SERVICE DATE",
name: "service_date"
},
{
label: "ADDRESS",
name: "address1"
},
{
label: "CREATE DATE",
name: "created_at"
}
],
queryParams: {
sort: [],
filters: [],
global_search: "",
per_page: 10,
page: 1
},
selected_rows: [],
total_rows: 0
};
},
methods: {
onNameSearch() {
this.selectedFilters = ["invoices", "workorder_estimates"];
this.fetchData();
},
clearTable(){
this.rows=[];
console.log(this.config.selected_rows_info);
this.config.selected_rows_info=false;
},
onChangeQuery(queryParams) {
console.log(queryParams);
this.queryParams = queryParams;
this.fetchData();
},
onRowClick(payload) {
console.log(payload);
},
setCustomer(selectedResult) {
this.selectedCustomer = selectedResult.customer.company
? `${selectedResult.customer.company + " - "}${
selectedResult.customer.fname
} ${selectedResult.customer.lname}`
: `${selectedResult.customer.fname} ${selectedResult.customer.lname}`;
this.selectedFirstName = selectedResult.customer.fname;
this.selectedLastName = selectedResult.customer.lname;
},
changeCheck(event, index, value) {
var checked = event.target.checked;
switch (value) {
case "invoices":
if (checked) {
this.selectedFields.push({ name: "Invoice", value: "invoices" });
this.invoiceChecked = true;
var data = this.filters[index];
data.is_checked = true;
Vue.set(this.filters, data, index);
} else {
var index = this.selectedFields.findIndex(
item => item.value === value
);
this.selectedFields.splice(index, 1);
this.invoiceChecked = false;
var data = this.filters[index];
data.is_checked = false;
Vue.set(this.filters, data, index);
}
break;
case "workorder_estimates":
if (checked) {
this.selectedFields.push({
name: "Estimate",
value: "workorder_estimates"
});
var data = this.filters[index];
data.is_checked = true;
Vue.set(this.filters, data, index);
} else {
var index = this.selectedFields.findIndex(
item => item.value === value
);
this.selectedFields.splice(index, 1);
this.estimateChecked = false;
var data = this.filters[index];
data.is_checked = false;
Vue.set(this.filters, data, index);
}
break;
}
},
removeFilter(index, value) {
switch (value) {
case "workorder_estimates":
this.selectedFields.splice(index, 1);
this.estimateChecked = false;
var i = this.filters.findIndex(item => item.value === value);
var data = this.filters[i];
data.is_checked = false;
Vue.set(this.filters, data, i);
break;
case "invoices":
this.selectedFields.splice(index, 1);
this.invoiceChecked = false;
var i = this.filters.findIndex(item => item.value === value);
var data = this.filters[i];
data.is_checked = false;
Vue.set(this.filters, data, i);
break;
}
},
updateFilters() {
this.selectedFilters = [];
this.selectedFields.forEach(element => {
this.selectedFilters.push(element.value);
});
if(this.selectedFilters.length == 0){
this.selectedFilters = ['invoices', 'workorder_estimates'];
}
this.fetchData();
},
async fetchData() {
var final = [];
try {
var result = await http.post("/estimate-invoice-search", {
type: this.selectedFilters,
search: {
value: this.selectedFirstName + " " + this.selectedLastName
},
per_page: this.queryParams.per_page,
page: this.queryParams.page,
sort: this.queryParams.sort
});
this.total_rows = result.recordsFiltered;
result.data.forEach(element => {
element.full_name = element.first_name + " " + element.last_name;
final.push(element);
});
this.rows = final;
} catch (error) {
console.log(error);
}
}
},
mounted() {}
};
</script>
now the method named clearTable here i want to reset my table to the point lie we see on page refresh in the method i used this.rows=[]; this clears all the rows which is exactly what i want but the text which shows the number of rows is still their and i cant seem to remove it please view the below image for clarification
i read the documentation on link but cant seem to find a solution for hiding the text, is their any way?
It looks like you're using total_rows as the variable for the number of rows in your template here:
<span>{{total_rows}}</span> Result(s)
The only spot in code that you set this value is in fetchData() where you set:
this.total_rows = result.recordsFiltered;
You can either:
1) Make total_rows a computed property (recommended) that returns the length of rows (I believe rows is always the same length as total_rows from your code)
-or-
2) Just set this.total_rows = 0; in your clearTable() function

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

I am trying to use the Rally App Catalog and the custom list app
I would like to add an additional selector (item type) and rename some of the headers on the fly. First I just want the sample to run. As it currently stands, it looks broken to me.
When I use the rally-app-builder build function and then paste the result app.html into a custom app I get a blank page with working app settings.
Here the uncompressed version from my initial attempt.
<script src="https://rally1.rallydev.com/apps/2.1/sdk-debug.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>Custom List</title>
<script type="text/javascript" src="/apps/2.1/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function() {
(function() {
var Ext = window.Ext4 || window.Ext;
var getHiddenFieldConfig = function(name) {
return {
name: name,
xtype: 'rallytextfield',
hidden: true,
handlesEvents: {
typeselected: function(type) {
this.setValue(null);
}
}
};
};
Ext.define('Rally.apps.customlist.Settings', {
singleton: true,
requires: [
'Rally.ui.combobox.FieldComboBox',
'Rally.ui.combobox.ComboBox',
'Rally.ui.CheckboxField'
],
getFields: function(app) {
this.app = app;
return [{
name: 'type',
xtype: 'rallycombobox',
allowBlank: false,
autoSelect: false,
shouldRespondToScopeChange: true,
context: this.app.getContext(),
initialValue: 'HierarchicalRequirement',
storeConfig: {
model: Ext.identityFn('TypeDefinition'),
sorters: [{
property: 'DisplayName'
}],
fetch: ['DisplayName', 'ElementName', 'TypePath', 'Parent', 'UserListable'],
filters: [{
property: 'UserListable',
value: true
}],
autoLoad: false,
remoteSort: false,
remoteFilter: true
},
displayField: 'DisplayName',
valueField: 'TypePath',
listeners: {
select: function(combo) {
this.app.clearFiltersAndSharedViews();
combo.fireEvent('typeselected', combo.getRecord().get('TypePath'), combo.context);
},
scope: this
},
bubbleEvents: ['typeselected'],
readyEvent: 'ready',
handlesEvents: {
projectscopechanged: function(context) {
this.refreshWithNewContext(context);
}
}
}, {
type: 'query'
}, {
name: 'showControls',
xtype: 'rallycheckboxfield',
fieldLabel: 'Show Control Bar'
},
getHiddenFieldConfig('columnNames'),
getHiddenFieldConfig('order')
];
}
});
})();
(function() {
var Ext = window.Ext4 || window.Ext;
Ext.define('Rally.apps.customlist.CustomListApp', {
extend: 'Rally.app.GridBoardApp',
requires: [
'Deft.Promise',
'Rally.apps.customlist.Settings',
'Rally.data.BulkRecordUpdater',
'Rally.data.ModelTypes',
'Rally.data.PreferenceManager',
'Rally.data.util.Sorter',
'Rally.data.wsapi.Filter',
'Rally.ui.gridboard.plugin.GridBoardInlineFilterControl',
'Rally.ui.gridboard.plugin.GridBoardSharedViewControl',
'Rally.ui.notify.Notifier',
'Rally.util.String'
],
disallowedAddNewTypes: ['user', 'userprofile', 'useriterationcapacity', 'testcaseresult', 'task', 'scmrepository', 'project', 'changeset', 'change', 'builddefinition', 'build', 'program'],
orderedAllowedPageSizes: [10, 25, 50, 100, 200],
readOnlyGridTypes: ['build', 'change', 'changeset'],
statePrefix: 'customlist',
allowExpansionStateToBeSaved: false,
isEditable: true,
config: {
defaultSettings: {
showControls: true
}
},
initComponent: function() {
this.appName = 'CustomList-' + this.getAppId();
this.callParent(arguments);
},
getSettingsFields: function() {
return Rally.apps.customlist.Settings.getFields(this);
},
loadModelNames: function() {
this.modelNames = _.compact([this._getTypeSetting()]);
this._setColumnNames(this._getColumnNamesSetting());
return Deft.Promise.when(this.modelNames);
},
addGridBoard: function() {
this.callParent(arguments);
if (!this.getSetting('showControls')) {
this.gridboard.getHeader().hide();
}
},
loadGridBoard: function() {
if (_.isEmpty(this.modelNames)) {
Ext.defer(function() {
this.fireEvent('settingsneeded', this);
this.publishComponentReady();
}, 1, this);
} else {
this.enableAddNew = this._shouldEnableAddNew();
this.enableRanking = this._shouldEnableRanking();
this.callParent(arguments);
}
},
getGridConfig: function() {
var config = _.merge(this.callParent(arguments), {
allColumnsStateful: true,
enableEditing: !_.contains(this.readOnlyGridTypes, this._getTypeSetting().toLowerCase()),
listeners: {
beforestaterestore: this._onBeforeGridStateRestore,
beforestatesave: this._onBeforeGridStateSave,
scope: this
},
pagingToolbarCfg: {
hidden: !this.getSetting('showControls'),
pageSizes: this.orderedAllowedPageSizes
}
});
var invalidQueryFilters = Rally.util.Filter.findInvalidSubFilters(this._getQueryFilter(), this.models);
if (invalidQueryFilters.length) {
config.store.on('beforeload', function(store) {
Ext.defer(function() {
store.fireEvent('load', store, store.getRootNode(), [], true);
}, 1);
return false;
});
this._showInvalidQueryMessage(config, _.map(invalidQueryFilters, function(filter) {
return 'Could not find the attribute "' + filter.property.split('.')[0] + '" on type "' + this.models[0].displayName + '" in the query segment "' + filter.toString() + '"';
}, this));
}
return config;
},
getColumnCfgs: function() {
return _.union(this.callParent(arguments), _.isEmpty(this.columnNames) && this.enableRanking ? ['DragAndDropRank'] : []);
},
getFilterControlConfig: function() {
return _.merge(this.callParent(arguments), {
listeners: {
beforestaterestore: {
fn: this._onBeforeFilterButtonStateRestore,
scope: this
}
}
});
},
getGridBoardCustomFilterControlConfig: function() {
var context = this.getContext();
var isArtifactModel = this.models[0].isArtifact();
var blackListFields = isArtifactModel ? ['ModelType', 'PortfolioItemType'] : ['ArtifactSearch', 'ModelType'];
var whiteListFields = isArtifactModel ? ['Milestones', 'Tags'] : [];
if (this.models[0].isProject()) {
blackListFields.push('SchemaVersion');
} else if (this.models[0].isRelease()) {
blackListFields.push('ChildrenPlannedVelocity', 'Version');
}
var config = {
ptype: 'rallygridboardinlinefiltercontrol',
inlineFilterButtonConfig: {
stateful: true,
stateId: context.getScopedStateId('custom-list-inline-filter'),
legacyStateIds: [
this.getScopedStateId('owner-filter'),
this.getScopedStateId('custom-filter-button')
],
filterChildren: true,
inlineFilterPanelConfig: {
quickFilterPanelConfig: {
defaultFields: isArtifactModel ? ['ArtifactSearch', 'Owner'] : [],
addQuickFilterConfig: {
blackListFields: blackListFields,
whiteListFields: whiteListFields
}
},
advancedFilterPanelConfig: {
advancedFilterRowsConfig: {
propertyFieldConfig: {
blackListFields: blackListFields,
whiteListFields: whiteListFields
}
}
}
}
}
};
if (isArtifactModel) {
config.inlineFilterButtonConfig.modelNames = this.modelNames;
} else {
config.inlineFilterButtonConfig.model = this.models[0];
}
return config;
},
getSharedViewConfig: function() {
var context = this.getContext();
return {
ptype: 'rallygridboardsharedviewcontrol',
sharedViewConfig: {
stateful: true,
stateId: context.getScopedStateId('custom-list-shared-view'),
enableUrlSharing: this.isFullPageApp !== false
}
};
},
getGridBoardConfig: function() {
var config = this.callParent(arguments);
return _.merge(config, {
listeners: {
viewchange: function() {
this.loadGridBoard();
},
filterchange: function() {
this.gridboard.getGridOrBoard().noDataPrimaryText = undefined;
this.gridboard.getGridOrBoard().noDataSecondaryText = undefined;
},
scope: this
}
});
},
onTreeGridReady: function(grid) {
if (grid.store.getTotalCount() > 10) {
this.gridboard.down('#pagingToolbar').show();
}
this.callParent(arguments);
},
getGridStoreConfig: function() {
var sorters = this._getValidSorters(Rally.data.util.Sorter.sorters(this.getSetting('order')));
if (_.isEmpty(sorters)) {
var rankField = this.getContext().getWorkspace().WorkspaceConfiguration.DragDropRankingEnabled ? 'DragAndDropRank' : 'Rank';
var defaultSort = Rally.data.ModelTypes.areArtifacts(this.modelNames) ? rankField : Rally.data.util.Sorter.getDefaultSort(this.modelNames[0]);
sorters = Rally.data.util.Sorter.sorters(defaultSort);
}
return {
listeners: {
warning: {
fn: this._onGridStoreWarning,
scope: this
}
},
pageSize: 10,
sorters: sorters
};
},
getAddNewConfig: function() {
var config = {
minWidth: 700,
openEditorAfterAddFailure: false,
margin: 0
};
if (!this.getContext().isFeatureEnabled('F6971_REACT_DASHBOARD_PANELS')) {
config.disableAddButton = this.appContainer.slug === 'incompletestories';
}
return _.merge(this.callParent(arguments), config);
},
getFieldPickerConfig: function() {
return _.merge(this.callParent(arguments), {
buttonConfig: {
disabled: !this._userHasPermissionsToEditPanelSettings()
},
gridAlwaysSelectedValues: function() {
return [];
},
gridFieldBlackList: this._getTypeSetting().toLowerCase() === 'task' ? ['Rank'] : []
});
},
getPermanentFilters: function() {
return this._getQueryFilter().concat(this._getTimeboxScopeFilter()).concat(this._getProjectFilter());
},
onTimeboxScopeChange: function() {
this.callParent(arguments);
this.loadGridBoard();
},
clearFiltersAndSharedViews: function() {
var context = this.getContext();
if (this.gridboard) {
this.gridboard.down('rallyinlinefilterpanel').clear();
this.gridboard.down('rallysharedviewcombobox').reset();
}
Ext.create('Rally.data.wsapi.Store', {
model: Ext.identityFn('preference'),
autoLoad: true,
filters: [{
property: 'AppId',
value: context.getAppId()
}, {
property: 'Type',
value: 'View'
}, {
property: 'Workspace',
value: context.getWorkspace()._ref
}],
context: context.getDataContext(),
listeners: {
load: function(store, records) {
if (!_.isEmpty(records)) {
var batchStore = Ext.create('Rally.data.wsapi.batch.Store', {
requester: this,
data: records
});
batchStore.removeAll();
batchStore.sync();
}
store.destroyStore();
},
scope: this
}
});
},
_getTypeSetting: function() {
return this.getSetting('type') || this.getSetting('url');
},
_getColumnNamesSetting: function() {
return this.getSetting('columnNames') ||
(this.getSetting('fetch') || '').split(',');
},
_getQueryFilter: function() {
var query = new Ext.Template(this.getSetting('query')).apply({
projectName: this.getContext().getProject().Name,
projectOid: this.getContext().getProject().ObjectID,
user: this.getContext().getUser()._ref
});
if (query) {
try {
return [Rally.data.wsapi.Filter.fromQueryString(query)];
} catch (e) {
Rally.ui.notify.Notifier.showError({
message: e.message
});
}
}
return [];
},
_getProjectFilter: function() {
return this.modelNames[0].toLowerCase() === 'milestone' ? [
Rally.data.wsapi.Filter.or([{
property: 'Projects',
operator: 'contains',
value: this.getContext().getProjectRef()
}, {
property: 'TargetProject',
operator: '=',
value: null
}])
] : [];
},
_getTimeboxScopeFilter: function() {
var timeboxScope = this.getContext().getTimeboxScope();
var hasTimeboxField = timeboxScope && _.any(this.models, timeboxScope.isApplicable, timeboxScope);
return hasTimeboxField ? [timeboxScope.getQueryFilter()] : [];
},
_shouldEnableAddNew: function() {
return !_.contains(this.disallowedAddNewTypes, this._getTypeSetting().toLowerCase());
},
_shouldEnableRanking: function() {
return this._getTypeSetting().toLowerCase() !== 'task';
},
_setColumnNames: function(columnNames) {
this.columnNames = _.compact(_.isString(columnNames) ? columnNames.split(',') : columnNames);
},
_onBeforeFilterButtonStateRestore: function(filterButton, state) {
if (state && state.filters && state.filters.length) {
var stateFilters = _.map(state.filters, function(filterStr) {
return Rally.data.wsapi.Filter.fromQueryString(filterStr);
});
var validFilters = Rally.util.Filter.removeNonapplicableTypeSpecificFilters(stateFilters, this.models);
state.filters = _.invoke(validFilters, 'toString');
}
},
_hasViewSelected: function() {
var sharedViewConfig = this.getSharedViewConfig().sharedViewConfig;
if (sharedViewConfig && sharedViewConfig.stateId) {
var value = (Ext.state.Manager.get(sharedViewConfig.stateId) || {}).value;
return !_.isEmpty(value);
}
return false;
},
_onBeforeGridStateRestore: function(grid, state) {
if (!state) {
return;
}
if (state.columns) {
var appScopedColumnNames = this._getValidUuids(grid, this.getColumnCfgs());
var userScopedColumnNames = this._getValidUuids(grid, state.columns);
if (this._hasViewSelected()) {
state.columns = userScopedColumnNames;
} else {
// Get the columns that are present in the app scope and not in the user scope
var differingColumns = _.difference(appScopedColumnNames, userScopedColumnNames);
// If there are columns in the app scope that are not in the
// user scope, append them to the user scope to preserve
// user scope column order
if (differingColumns.length > 0) {
state.columns = state.columns.concat(differingColumns);
}
// Filter out any columns that are in the user scope that are not in the
// app scope
state.columns = _.filter(state.columns, function(column) {
return _.contains(appScopedColumnNames, _.isObject(column) ? column.dataIndex : column);
}, this);
}
}
if (state.sorters) {
state.sorters = this._getValidSorters(state.sorters);
if (_.isEmpty(state.sorters)) {
delete state.sorters;
}
}
},
_getValidUuids: function(grid, columns) {
return _.reduce(columns, function(result, column) {
var dataIndex = this._getColumnDataIndex(column);
var field = this._getModelField(grid, dataIndex);
if (field) {
result.push(dataIndex);
}
return result;
}, [], this);
},
_getModelField: function(grid, dataIndex) {
return grid.getModels()[0].getField(dataIndex);
},
_getColumnDataIndex: function(column) {
return _.isObject(column) ? column.dataIndex : column;
},
_onBeforeGridStateSave: function(grid, state) {
var newColumnNames = this._getColumnNamesFromState(state);
if (!_.isEmpty(newColumnNames)) {
this._setColumnNames(newColumnNames);
if (this._userHasPermissionsToEditPanelSettings()) {
this.updateSettingsValues({
settings: {
columnNames: newColumnNames.join(',')
}
});
}
}
},
_onGridStoreWarning: function(store, warnings, operation) {
var couldNotParseWarnings = _.filter(warnings, function(warning) {
return Rally.util.String.startsWith(warning, 'Could not parse ');
});
if (couldNotParseWarnings.length) {
_.assign(operation.resultSet, {
count: 0,
records: [],
total: 0,
totalRecords: 0
});
this._showInvalidQueryMessage(this.gridboard.getGridOrBoard(), couldNotParseWarnings);
}
},
_showInvalidQueryMessage: function(gridOrGridConfig, secondaryTextStrings) {
gridOrGridConfig.noDataPrimaryText = 'Invalid Query';
gridOrGridConfig.noDataSecondaryText = _.map(secondaryTextStrings, function(str) {
return '<div>' + str + '</div>';
}).join('');
},
_getValidSorters: function(sorters) {
return _.filter(sorters, function(sorter) {
return _.any(this.models, function(model) {
var field = model.getField(sorter.property);
return field && field.sortable;
});
}, this);
},
_userHasPermissionsToEditPanelSettings: function() {
return this.isEditable;
},
_getColumnNamesFromState: function(state) {
return _(state && state.columns).map(function(newColumn) {
return _.isObject(newColumn) ? newColumn.dataIndex : newColumn;
}).compact().value();
}
});
})();
Rally.launchApp('Rally.apps.customlist.CustomListApp', {
name: "Custom List",
parentRepos: ""
});
});
</script>
</head>
<body>
</body>
</html>
Ok, I got results by commenting out the following lines in getAddNewConfig:
getAddNewConfig: function () {
var config = {
minWidth: 700,
openEditorAfterAddFailure: false,
margin: 0
};
//if(!this.getContext().isFeatureEnabled('F6971_REACT_DASHBOARD_PANELS')) {
// config.disableAddButton = this.appContainer.slug === 'incompletestories';
//}
return _.merge(this.callParent(arguments), config);
},
It seems that 'this.appContainer.slug' is undefined for me.
Here's the final working CustomListApp.js generated from the comment stream in the answer above. The main tweaks were deleting some old code checking a feature toggle and giving it a default type to display (defect in this case) in the defaultSettings block.
(function() {
var Ext = window.Ext4 || window.Ext;
Ext.define('Rally.apps.customlist.CustomListApp', {
extend: 'Rally.app.GridBoardApp',
requires: [
'Deft.Promise',
'Rally.apps.customlist.Settings',
'Rally.data.BulkRecordUpdater',
'Rally.data.ModelTypes',
'Rally.data.PreferenceManager',
'Rally.data.util.Sorter',
'Rally.data.wsapi.Filter',
'Rally.ui.gridboard.plugin.GridBoardInlineFilterControl',
'Rally.ui.gridboard.plugin.GridBoardSharedViewControl',
'Rally.ui.notify.Notifier',
'Rally.util.String'
],
disallowedAddNewTypes: ['user', 'userprofile', 'useriterationcapacity', 'testcaseresult', 'task', 'scmrepository', 'project', 'changeset', 'change', 'builddefinition', 'build', 'program'],
orderedAllowedPageSizes: [10, 25, 50, 100, 200],
readOnlyGridTypes: ['build', 'change', 'changeset'],
statePrefix: 'customlist',
allowExpansionStateToBeSaved: false,
isEditable: true,
config: {
defaultSettings: {
showControls: true,
type: 'defect' //important to include a default type to display
}
},
initComponent: function () {
this.appName = 'CustomList-' + this.getAppId();
if (this.defaultSettings.url) {
Ext.apply(this.defaultSettings, { type: this.defaultSettings.url });
}
this.callParent(arguments);
},
getSettingsFields: function() {
return Rally.apps.customlist.Settings.getFields(this);
},
loadModelNames: function () {
this.modelNames = _.compact(this.getTypeSetting());
this._setColumnNames(this._getColumnNamesSetting());
return Deft.Promise.when(this.modelNames);
},
addGridBoard: function () {
this.callParent(arguments);
if (!this.getSetting('showControls')) {
this.gridboard.getHeader().hide();
}
},
loadGridBoard: function () {
if (_.isEmpty(this.modelNames)) {
Ext.defer(function () {
this.fireEvent('settingsneeded', this);
this.publishComponentReady();
}, 1, this);
} else {
this.enableAddNew = this._shouldEnableAddNew();
this.enableRanking = this._shouldEnableRanking();
this.callParent(arguments);
}
},
getGridConfig: function () {
var config = _.merge(this.callParent(arguments), {
allColumnsStateful: true,
enableEditing: _.intersection(this.readOnlyGridTypes, this.getTypeSetting()).length === 0,
listeners: {
beforestaterestore: this._onBeforeGridStateRestore,
beforestatesave: this._onBeforeGridStateSave,
scope: this
},
pagingToolbarCfg: {
hidden: !this.getSetting('showControls'),
pageSizes: this.orderedAllowedPageSizes
}
});
var invalidQueryFilters = Rally.util.Filter.findInvalidSubFilters(this._getQueryFilter(), this.models);
if (invalidQueryFilters.length) {
config.store.on('beforeload', function (store) {
Ext.defer(function () {
store.fireEvent('load', store, store.getRootNode(), [], true);
}, 1);
return false;
});
this._showInvalidQueryMessage(config, _.map(invalidQueryFilters, function (filter) {
return 'Could not find the attribute "'+ filter.property.split('.')[0] +'" on type "'+ this.models[0].displayName +'" in the query segment "'+ filter.toString() + '"';
}, this));
}
return config;
},
getColumnCfgs: function() {
return _.union(this.callParent(arguments), _.isEmpty(this.columnNames) && this.enableRanking ? ['DragAndDropRank'] : []);
},
getFilterControlConfig: function () {
return _.merge(this.callParent(arguments), {
listeners: {
beforestaterestore: {
fn: this._onBeforeFilterButtonStateRestore,
scope: this
}
}
});
},
getGridBoardCustomFilterControlConfig: function() {
var context = this.getContext();
var isArtifactModel = this.models[0].isArtifact();
var blackListFields = isArtifactModel ? ['ModelType', 'PortfolioItemType', 'LastResult'] : ['ArtifactSearch', 'ModelType'];
var whiteListFields = isArtifactModel ? ['Milestones', 'Tags'] : [];
if (this.models[0].isProject()) {
blackListFields.push('SchemaVersion');
} else if (this.models[0].isRelease()) {
blackListFields.push('ChildrenPlannedVelocity', 'Version');
}
var config = {
ptype: 'rallygridboardinlinefiltercontrol',
inlineFilterButtonConfig: {
stateful: true,
stateId: context.getScopedStateId('custom-list-inline-filter'),
legacyStateIds: [
this.getScopedStateId('owner-filter'),
this.getScopedStateId('custom-filter-button')
],
filterChildren: true,
inlineFilterPanelConfig: {
quickFilterPanelConfig: {
defaultFields: isArtifactModel ? ['ArtifactSearch', 'Owner'] : [],
addQuickFilterConfig: {
blackListFields: blackListFields,
whiteListFields: whiteListFields
}
},
advancedFilterPanelConfig: {
advancedFilterRowsConfig: {
propertyFieldConfig: {
blackListFields: blackListFields,
whiteListFields: whiteListFields
}
}
}
}
}
};
if (isArtifactModel) {
config.inlineFilterButtonConfig.modelNames = this.modelNames;
} else {
config.inlineFilterButtonConfig.model = this.models[0];
}
return config;
},
getSharedViewConfig: function() {
var context = this.getContext();
return {
ptype: 'rallygridboardsharedviewcontrol',
sharedViewConfig: {
stateful: true,
stateId: context.getScopedStateId('custom-list-shared-view'),
enableUrlSharing: this.isFullPageApp !== false
}
};
},
getGridBoardConfig: function () {
var config = this.callParent(arguments);
return _.merge(config, {
listeners: {
viewchange: function() {
this.loadGridBoard();
},
filterchange: function() {
this.gridboard.getGridOrBoard().noDataPrimaryText = undefined;
this.gridboard.getGridOrBoard().noDataSecondaryText = undefined;
},
scope: this
}
});
},
onTreeGridReady: function (grid) {
if (grid.store.getTotalCount() > 10) {
this.gridboard.down('#pagingToolbar').show();
}
this.callParent(arguments);
},
getGridStoreConfig: function () {
var sorters = this._getValidSorters(Rally.data.util.Sorter.sorters(this.getSetting('order')));
if (_.isEmpty(sorters)) {
var rankField = this.getContext().getWorkspace().WorkspaceConfiguration.DragDropRankingEnabled ? 'DragAndDropRank' : 'Rank';
var defaultSort = Rally.data.ModelTypes.areArtifacts(this.modelNames) ? rankField : Rally.data.util.Sorter.getDefaultSort(this.modelNames[0]);
sorters = Rally.data.util.Sorter.sorters(defaultSort);
}
return {
listeners: {
warning: {
fn: this._onGridStoreWarning,
scope: this
}
},
pageSize: 10,
sorters: sorters
};
},
getAddNewConfig: function () {
var config = {
minWidth: 700,
openEditorAfterAddFailure: false,
margin: 0
};
return _.merge(this.callParent(arguments), config);
},
getFieldPickerConfig: function () {
return _.merge(this.callParent(arguments), {
buttonConfig: {
disabled: !this._userHasPermissionsToEditPanelSettings()
},
gridAlwaysSelectedValues: function () { return []; },
gridFieldBlackList: this._shouldEnableRanking() ? [] : ['Rank']
});
},
getPermanentFilters: function () {
return this._getQueryFilter().concat(this._getTimeboxScopeFilter()).concat(this._getProjectFilter());
},
onTimeboxScopeChange: function() {
this.callParent(arguments);
this.loadGridBoard();
},
clearFiltersAndSharedViews: function() {
var context = this.getContext();
if (this.gridboard) {
this.gridboard.down('rallyinlinefilterpanel').clear();
this.gridboard.down('rallysharedviewcombobox').reset();
}
Ext.create('Rally.data.wsapi.Store', {
model: Ext.identityFn('preference'),
autoLoad: true,
filters: [
{property: 'AppId', value: context.getAppId()},
{property: 'Type', value: 'View'},
{property: 'Workspace', value: context.getWorkspace()._ref}
],
context: context.getDataContext(),
listeners: {
load: function(store, records) {
if(!_.isEmpty(records)) {
var batchStore = Ext.create('Rally.data.wsapi.batch.Store', {
requester: this,
data: records
});
batchStore.removeAll();
batchStore.sync();
}
store.destroyStore();
},
scope: this
}
});
},
getTypeSetting: function() {
return (this.getSetting('type') || this.getSetting('url') || '').toLowerCase().split(',');
},
_getColumnNamesSetting: function() {
return this.getSetting('columnNames') ||
(this.getSetting('fetch') || '').split(',');
},
_getQueryFilter: function () {
var query = new Ext.Template(this.getSetting('query')).apply({
projectName: this.getContext().getProject().Name,
projectOid: this.getContext().getProject().ObjectID,
user: this.getContext().getUser()._ref
});
if (query) {
try {
return [ Rally.data.wsapi.Filter.fromQueryString(query) ];
} catch(e) {
Rally.ui.notify.Notifier.showError({ message: e.message });
}
}
return [];
},
_getProjectFilter: function () {
return this.modelNames[0].toLowerCase() === 'milestone' ? [
Rally.data.wsapi.Filter.or([
{ property: 'Projects', operator: 'contains', value: this.getContext().getProjectRef() },
{ property: 'TargetProject', operator: '=', value: null }
])
] : [];
},
_getTimeboxScopeFilter: function () {
var timeboxScope = this.getContext().getTimeboxScope();
var hasTimeboxField = timeboxScope && _.any(this.models, timeboxScope.isApplicable, timeboxScope);
return hasTimeboxField ? [ timeboxScope.getQueryFilter() ] : [];
},
_shouldEnableAddNew: function() {
return _.intersection(this.disallowedAddNewTypes, this.getTypeSetting()).length === 0;
},
_shouldEnableRanking: function() {
return !_.contains(this.getTypeSetting(), 'task');
},
_setColumnNames: function (columnNames) {
this.columnNames = _.compact(_.isString(columnNames) ? columnNames.split(',') : columnNames);
},
_onBeforeFilterButtonStateRestore: function (filterButton, state) {
if (state && state.filters && state.filters.length) {
var stateFilters = _.map(state.filters, function (filterStr) {
return Rally.data.wsapi.Filter.fromQueryString(filterStr);
});
var validFilters = Rally.util.Filter.removeNonapplicableTypeSpecificFilters(stateFilters, this.models);
state.filters = _.invoke(validFilters, 'toString');
}
},
_hasViewSelected: function() {
var sharedViewConfig = this.getSharedViewConfig().sharedViewConfig;
if (sharedViewConfig && sharedViewConfig.stateId) {
var value = (Ext.state.Manager.get(sharedViewConfig.stateId) || {}).value;
return !_.isEmpty(value);
}
return false;
},
_onBeforeGridStateRestore: function (grid, state) {
if (!state) {
return;
}
if (state.columns) {
var appScopedColumnNames = this._getValidUuids(grid, this.getColumnCfgs());
var userScopedColumnNames = this._getValidUuids(grid, state.columns);
if (this._hasViewSelected()) {
state.columns = userScopedColumnNames;
} else {
// Get the columns that are present in the app scope and not in the user scope
var differingColumns = _.difference(appScopedColumnNames, userScopedColumnNames);
// If there are columns in the app scope that are not in the
// user scope, append them to the user scope to preserve
// user scope column order
if (differingColumns.length > 0) {
state.columns = state.columns.concat(differingColumns);
}
// Filter out any columns that are in the user scope that are not in the
// app scope
state.columns = _.filter(state.columns, function (column) {
return _.contains(appScopedColumnNames, _.isObject(column) ? column.dataIndex : column);
}, this);
}
}
if (state.sorters) {
state.sorters = this._getValidSorters(state.sorters);
if (_.isEmpty(state.sorters)) {
delete state.sorters;
}
}
},
_getValidUuids: function(grid, columns) {
return _.reduce(columns, function(result, column) {
var dataIndex = this._getColumnDataIndex(column);
var field = this._getModelField(grid, dataIndex);
if (field) {
result.push(dataIndex);
}
return result;
}, [], this);
},
_getModelField: function(grid, dataIndex) {
return grid.getModels()[0].getField(dataIndex);
},
_getColumnDataIndex: function(column) {
return _.isObject(column) ? column.dataIndex : column;
},
_onBeforeGridStateSave: function (grid, state) {
var newColumnNames = this._getColumnNamesFromState(state);
if (!_.isEmpty(newColumnNames)) {
this._setColumnNames(newColumnNames);
if (this._userHasPermissionsToEditPanelSettings()) {
this.updateSettingsValues({
settings: {
columnNames: newColumnNames.join(',')
}
});
}
}
},
_onGridStoreWarning: function(store, warnings, operation) {
var couldNotParseWarnings = _.filter(warnings, function(warning){
return Rally.util.String.startsWith(warning, 'Could not parse ');
});
if(couldNotParseWarnings.length) {
_.assign(operation.resultSet, {
count: 0,
records: [],
total: 0,
totalRecords: 0
});
this._showInvalidQueryMessage(this.gridboard.getGridOrBoard(), couldNotParseWarnings);
}
},
_showInvalidQueryMessage: function(gridOrGridConfig, secondaryTextStrings) {
gridOrGridConfig.noDataPrimaryText = 'Invalid Query';
gridOrGridConfig.noDataSecondaryText = _.map(secondaryTextStrings, function(str){
return '<div>' + str + '</div>';
}).join('');
},
_getValidSorters: function (sorters) {
return _.filter(sorters, function (sorter) {
return _.any(this.models, function (model) {
var field = model.getField(sorter.property);
return field && field.sortable;
});
}, this);
},
_userHasPermissionsToEditPanelSettings: function () {
return this.isEditable;
},
_getColumnNamesFromState: function (state) {
return _(state && state.columns).map(function (newColumn) {
return _.isObject(newColumn) ? newColumn.dataIndex : newColumn;
}).compact().value();
}
});
})();