Extjs4 how to change the store in treepanel - model-view-controller

I have some menu tree stores in 'app/store/', such as 'menu1.js' , 'menu2.js'.
In my web page, I have a navigation bar at top region with many buttons to control the menu tree at left region to change the menu. But I do not know how to change the store in this tree panel.
Here is my code:
app/store/Menus1.js
Ext.define('Crm.store.Menus1', {
extend: 'Ext.data.TreeStore',
root: {
expanded: true,
children: [{
text: "subroot1",
expanded: true,
children:[
{ id: 'item01', text: "item1", leaf: true },
{ id: 'item02', text: "item2", leaf: true },
]
}, {
text: "subroot2",,
expanded: true,
children: [
{ id: 'item03', text: "item3", leaf: true },
{ id: 'item04', text: "item4", leaf: true }
]
}]
}
});
app/store/Menus2.js
Ext.define('Crm.store.Menus2', {
extend: 'Ext.data.TreeStore',
root: {
expanded: true,
children: [{
text: "subroot1",
expanded: true,
children:[
{ id: 'item05', text: "item5", leaf: true },
{ id: 'item06', text: "item6", leaf: true },
]
}, {
text: "subroot2",,
expanded: true,
children: [
{ id: 'item07', text: "item7", leaf: true },
{ id: 'item08', text: "item8", leaf: true }
]
}]
}
});
app/view/MenuBar.js (ie: I do not set its store)
Ext.define('Crm.view.MenuBar', {
extend: "Ext.panel.Panel",
alias:'widget.crm_menubar',
initComponent: function() {
Ext.apply(this, {
id: 'crm-menuBar',
xtype:'panel',
width: 212,
frameHeader:false,
hideCollapseTool:true,
split: true,
collapsible:true,
collapseMode: 'mini',
items: [
Ext.create('Ext.tree.Panel', {
id: 'menutree',
border: false,
margin:'5 4 0 4',
height: '98%',
rootVisible: false,
}),
]
});
this.callParent();
}
});
app/controller/Navi.js
Ext.define('Crm.controller.Navi', {
extend: 'Ext.app.Controller',
requires: [ 'Crm.store.Menus1', 'Crm.store.Menus2' ],
stores: [ 'Menus1','Menus2' ],
refs: [
{ ref: 'crm_naviationBar', selector: 'crm_naviationBar' },
{ ref: 'crm_menubar', selector: 'crm_menubar' }
],
init: function(){
var menutree = Ext.getCmp('menutree');
var menustore = menutree.getStore();
menustore = Menus1; // I want initial the menu's store with Menus1
//menustore = Ext.create('Crm.store.Menus1');
this.control({
'button': {
click: this.onItemClicked,
scope: this
}
});
},
onItemClicked: function(btn,eventObj){
var menustore = Ext.getCmp('menutree').getStore();
var btnId = btn.getId();
if (btnId == 'btn_menus1') { //When button1 is clicked, change menu to menus1
menustore = Menus1;
//menustore = Ext.create('Crm.store.Menus1');
} else if (btnId == 'btn_menus2') { //When button2 is clicked, change menu to menus2
menustore = Menus2;
//menustore = Ext.create('Crm.store.Menus1');
}
}
});

Unfortunately, this is a bug that Sencha has ignored for many, many months :(
http://www.sencha.com/forum/showthread.php?143843-Calling-reconfigure%28%29-on-Ext.tree.Panel-throws-error

You should be able to use the reconfigure() method on the TreePanel after getting your store. This is the same process you would use to change the store of a grid.
var store = Ext.getStore('newStore');
var menutree = Ext.getCmp('menutree');
menutree.reconfigure(store);
Full documentation here: Ext.Tree.Panel reconfigure method.
Note: There is a comment in the documentation stating that this method does not work, but it doesn't specify a version of ExtJS. You may have to wait for an updated version of the framework before it works as documented.

Please see this thread:
http://www.sencha.com/forum/showthread.php?143843-Calling-reconfigure%28%29-on-Ext.tree.Panel-throws-error
According to the latest 4.1 docs, reconfigure() has been removed from the Ext.tree.Panel.
Note: However, strangely enough calling reconfigure() will not give an error, meaning the method still exists, but I can't get it to work correctly as it returns another error.
Also see the discussion in the comments of 4.0:
http://docs.sencha.com/ext-js/4-0/#!/api/Ext.tree.Panel-method-reconfigure

I extended the default treestore and wrote setStore() method like below. Do not pretend that this is the best solution, but it works for ExtJS 4.1.1 correctly. I believe do not requires a lot of changes to support other versions.
setStore: function(store) {
var me=this,
view;
view = me.getView();
me.removeManagedListener('beforeload');
me.removeManagedListener('load');
me.store.clearListeners();
me.store.clearManagedListeners();
me.store = store;
if (Ext.isString(me.store)) {
me.store = Ext.StoreMgr.lookup(me.store);
} else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
me.store = new Ext.data.TreeStore(Ext.apply({}, me.store || {}, {
root: me.root,
fields: me.fields,
model: me.model,
folderSort: me.folderSort
}));
} else if (me.root) {
me.store = Ext.data.StoreManager.lookup(me.store);
me.store.setRootNode(me.root);
if (me.folderSort !== undefined) {
me.store.folderSort = me.folderSort;
me.store.sort();
}
}
view.store.treeStore = me.store;
view.setRootNode(me.store.getRootNode());
me.mon(me.store, {
scope: me,
rootchange: me.onRootChange,
clear: me.onClear
});
me.relayEvents(me.store, [
/**
* #event beforeload
* #inheritdoc Ext.data.TreeStore#beforeload
*/
'beforeload',
/**
* #event load
* #inheritdoc Ext.data.TreeStore#load
*/
'load'
]);
me.mon(me.store, {
/**
* #event itemappend
* #inheritdoc Ext.data.TreeStore#append
*/
append: me.createRelayer('itemappend'),
/**
* #event itemremove
* #inheritdoc Ext.data.TreeStore#remove
*/
remove: me.createRelayer('itemremove'),
/**
* #event itemmove
* #inheritdoc Ext.data.TreeStore#move
*/
move: me.createRelayer('itemmove', [0, 4]),
/**
* #event iteminsert
* #inheritdoc Ext.data.TreeStore#insert
*/
insert: me.createRelayer('iteminsert'),
/**
* #event beforeitemappend
* #inheritdoc Ext.data.TreeStore#beforeappend
*/
beforeappend: me.createRelayer('beforeitemappend'),
/**
* #event beforeitemremove
* #inheritdoc Ext.data.TreeStore#beforeremove
*/
beforeremove: me.createRelayer('beforeitemremove'),
/**
* #event beforeitemmove
* #inheritdoc Ext.data.TreeStore#beforemove
*/
beforemove: me.createRelayer('beforeitemmove'),
/**
* #event beforeiteminsert
* #inheritdoc Ext.data.TreeStore#beforeinsert
*/
beforeinsert: me.createRelayer('beforeiteminsert'),
/**
* #event itemexpand
* #inheritdoc Ext.data.TreeStore#expand
*/
expand: me.createRelayer('itemexpand', [0, 1]),
/**
* #event itemcollapse
* #inheritdoc Ext.data.TreeStore#collapse
*/
collapse: me.createRelayer('itemcollapse', [0, 1]),
/**
* #event beforeitemexpand
* #inheritdoc Ext.data.TreeStore#beforeexpand
*/
beforeexpand: me.createRelayer('beforeitemexpand', [0, 1]),
/**
* #event beforeitemcollapse
* #inheritdoc Ext.data.TreeStore#beforecollapse
*/
beforecollapse: me.createRelayer('beforeitemcollapse', [0, 1])
});
// If the root is not visible and there is no rootnode defined, then just lets load the store
if (!view.rootVisible && !me.getRootNode()) {
me.setRootNode({
expanded: true
});
}
}

Related

FullCalendar - can't retrieve json data, and display events on calendar

I have a "calendar.js" file which initializes the calendar and retrieves the events in json on the route "{{route ('allCourse')}}". unfortunately the js file cannot access the route in order to retrieve the events (error 404). However, I manage to retrieve them via the url in the browser. Please help me, thank you.
the error - xhr
Request URL: http://univinfo.test/admin/%7B%7B%20route('allCourse')%20%7D%7D?start=2020-10-01&end=2020-11-01&_=1601837725221
Request Method: GET
State code: 404 Not Found
Remote address: 127.0.0.1:80
Access point strategy: no-referrer-when-downgrade
calendar.js
$(function () {
'use strict'
// Initialize tooltip
$('[data-toggle="tooltip"]').tooltip()
// Sidebar calendar
$('#calendarInline').datepicker({
showOtherMonths: true,
selectOtherMonths: true,
beforeShowDay: function (date) {
// add leading zero to single digit date
var day = date.getDate();
console.log(day);
return [true, (day < 10 ? 'zero' : '')];
}
});
// Initialize fullCalendar
$('#calendar').fullCalendar({
height: 'parent',
locale: 'fr',
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay,listWeek'
},
navLinks: true,
selectable: true,
selectLongPressDelay: 100,
editable: true,
nowIndicator: true,
defaultView: 'listMonth',
views: {
agenda: {
columnHeaderHtml: function (mom) {
return '<span>' + mom.format('ddd') + '</span>' +
'<span>' + mom.format('DD') + '</span>';
}
},
day: {
columnHeader: false
},
listMonth: {
listDayFormat: 'ddd DD',
listDayAltFormat: false
},
listWeek: {
listDayFormat: 'ddd DD',
listDayAltFormat: false
},
agendaThreeDay: {
type: 'agenda',
duration: {
days: 3
},
titleFormat: 'MMMM YYYY'
}
},
events: "{{ route('allCourse') }}",
eventAfterAllRender: function (view) {
if (view.name === 'listMonth' || view.name === 'listWeek') {
var dates = view.el.find('.fc-list-heading-main');
dates.each(function () {
var text = $(this).text().split(' ');
var now = moment().format('DD');
$(this).html(text[0] + '<span>' + text[1] + '</span>');
if (now === text[1]) {
$(this).addClass('now');
}
});
}
console.log(view.el);
},
eventRender: function (event, element) {
if (event.description) {
element.find('.fc-list-item-title').append('<span class="fc-desc">' + event.description + '</span>');
element.find('.fc-content').append('<span class="fc-desc">' + event.description + '</span>');
}
var eBorderColor = (event.source.borderColor) ? event.source.borderColor : event.borderColor;
element.find('.fc-list-item-time').css({
color: eBorderColor,
borderColor: eBorderColor
});
element.find('.fc-list-item-title').css({
borderColor: eBorderColor
});
element.css('borderLeftColor', eBorderColor);
},
});
var calendar = $('#calendar').fullCalendar('getCalendar');
// change view to week when in tablet
if (window.matchMedia('(min-width: 576px)').matches) {
calendar.changeView('agendaWeek');
}
// change view to month when in desktop
if (window.matchMedia('(min-width: 992px)').matches) {
calendar.changeView('month');
}
// change view based in viewport width when resize is detected
calendar.option('windowResize', function (view) {
if (view.name === 'listWeek') {
if (window.matchMedia('(min-width: 992px)').matches) {
calendar.changeView('month');
} else {
calendar.changeView('listWeek');
}
}
});
// Display calendar event modal
calendar.on('eventClick', function (calEvent, jsEvent, view) {
var modal = $('#modalCalendarEvent');
modal.modal('show');
modal.find('.event-title').text(calEvent.title);
if (calEvent.description) {
modal.find('.event-desc').text(calEvent.description);
modal.find('.event-desc').prev().removeClass('d-none');
} else {
modal.find('.event-desc').text('');
modal.find('.event-desc').prev().addClass('d-none');
}
modal.find('.event-start-date').text(moment(calEvent.start).format('LLL'));
modal.find('.event-end-date').text(moment(calEvent.end).format('LLL'));
//styling
modal.find('.modal-header').css('backgroundColor', (calEvent.source.borderColor) ? calEvent.source.borderColor : calEvent.borderColor);
});
//display current date
var dateNow = calendar.getDate();
calendar.option('select', function (startDate, endDate) {
$('#modalCreateEvent').modal('show');
$('#eventStartDate').val(startDate.format('LL'));
$('#eventEndDate').val(endDate.format('LL'));
$('#eventStartTime').val('07:00:00').trigger('change');
$('#eventEndTime').val('10:00:00').trigger('change');
});
$('.select2-modal').select2({
minimumResultsForSearch: Infinity,
dropdownCssClass: 'select2-dropdown-modal',
});
$('.calendar-add').on('click', function (e) {
e.preventDefault()
$('#modalCreateEvent').modal('show');
});
})
web.php
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Laravel Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
/* Courses Routes */
Route::get('index', 'CoursesController#index')->name('allCourse');
when I access the "allCourse" route directly via the url
{
"backgroundColor": "rgba(91,71,251,.2)",
"borderColor": "#5b47fb",
"events": [{
"start": "2020-10-07T07:00:00",
"end": "2020-10-07T10:00:00",
"title": "statistiques",
"description": "drink Coffee"
}]
};

Changing Magento2 address list on checkout

I have this code on Magento_NegotiableQuote/web/js/view/shipping-address/list.js
define([
'jquery',
'ko',
'underscore',
'mageUtils',
'uiLayout',
'Magento_Checkout/js/view/shipping-address/list',
'Magento_Customer/js/model/address-list',
'./address-renderer/address',
'mage/translate',
'select2',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/checkout-data',
'Magento_Checkout/js/action/create-shipping-address',
'Magento_Checkout/js/action/select-shipping-address',
'Magento_Checkout/js/action/select-shipping-method',
'Magento_Customer/js/model/customer',
], function (
$,
ko,
_,
utils,
layout,
ListView,
addressList,
AddressModel,
$t,
quote,
checkoutData,
createShippingAddress,
selectedShippingAddress,
customer,
) {
'use strict';
var defaultRendererTemplate = {
parent: '${ $.$data.parentName }',
name: '${ $.$data.name }',
component: 'Magento_NegotiableQuote/js/view/shipping-address/address-renderer/default'
},
popUp = null,
newShippingAddressOption = {
getAddressInline: function () {
return $t('New Address');
},
customerAddressId: null
},
shippingAddressOptions = addressList().filter(function (address) {
return address.getType() == 'customer-address';
});
shippingAddressOptions.push(newShippingAddressOption);
return ListView.extend({
defaults: {
template: 'Magento_NegotiableQuote/shipping-address/list'
},
shippingAddressOptions: shippingAddressOptions,
/** #inheritdoc */
initChildren: function () {
if (!checkoutConfig.isAddressInAddressBook && checkoutConfig.quoteShippingAddress) {
addressList.push(new AddressModel(checkoutConfig.quoteShippingAddress));
this.visible = true;
}
_.each(addressList(), this.createRendererComponent, this);
return this;
},
initialize: function () {
var self = this;
this._super()
.observe({
selectedShippingAddress: null,
isShippingAddressFormVisible: !customer.isLoggedIn() || shippingAddressOptions.length == 1
});
return this;
},
/**
* #param {Object} address
* #return {*}
*/
shippingAddressOptionsText: function (address) {
return address.getAddressInline();
},
/**
* #param {Object} address
*/
onShippingAddressChange: function (address) {
this.isShippingAddressFormVisible(address == newShippingAddressOption); //eslint-disable-line eqeqeq
},
/**
* Create new component that will render given address in the address list.
*
* #param {Object} address
* #param {*} index
*/
createRendererComponent: function (address, index) {
var rendererTemplate, templateData, rendererComponent;
if (index in this.rendererComponents) {
this.rendererComponents[index].address(address);
} else {
// rendererTemplates are provided via layout
rendererTemplate = address.getType() !== undefined &&
this.rendererTemplates[address.getType()] !== undefined ?
utils.extend({}, defaultRendererTemplate, this.rendererTemplates[address.getType()])
: defaultRendererTemplate;
templateData = {
parentName: this.name,
name: index
};
rendererComponent = utils.template(rendererTemplate, templateData);
utils.extend(rendererComponent, {
address: ko.observable(address)
});
rendererComponent = utils.template(rendererTemplate, templateData);
utils.extend(rendererComponent, {
address: ko.observable(address)
});
layout([rendererComponent]);
this.rendererComponents[index] = rendererComponent;
}
},
updateShippingAddress: function () {
if (this.selectedShippingAddress() && this.selectedShippingAddress() != newAddressOption) {
selectShippingAddress(this.selectedShippingAddress());
checkoutData.setSelectedShippingAddress(this.selectedShippingAddress().getKey());
// this.isShippingMethodFormVisible(true);
} else {
this.source.set('params.invalid', false);
this.source.trigger('shippingAddress.data.validate');
if (!this.source.get('params.invalid')) {
var addressData = this.source.get('shippingAddress');
// if user clicked the checkbox, its value is true or false. Need to convert.
addressData.save_in_address_book = this.saveInAddressBook ? 1 : 0;
// New address must be selected as a shipping address
var newShippingAddress = createShippingAddress(addressData);
selectShippingAddress(newShippingAddress);
checkoutData.setSelectedShippingAddress(newShippingAddress.getKey());
checkoutData.setNewCustomerShippingAddress(addressData);
//this.isShippingMethodFormVisible(true);
}
}
},
/**
* Added Select2 to dropdown
*/
selectTwo:function(){
$('select').select2({
theme: "classic"
});
},
});
});
Magento_NegotiableQuote/web/template/shipping-address/list.html
<div class="field addresses">
<div class="control">
<div class="shipping-address-items">
<select class="select2" name="billing_address_id" data-bind="
options: shippingAddressOptions,
optionsText: shippingAddressOptionsText,
value: selectedShippingAddress,
event: {change: onShippingAddressChange(selectedShippingAddress())};"
afterRender="selectTwo()"></select>
</div>
</div>
</div>
Was able to change the address list to dropdown select, However, I want to have an output to look like this
Any help is appreciated, How can I add an action after I select an address? I am kinda new with Knock Out js is there any way I can display the address box after I select the address on the dropdown?

Magento v1.8.1.0 Cannot upload images

SOLVED: I figured out that it wasn't creating the image containers properly, I editted the onFilesSubmitted function to be as such:
onFilesSubmitted: function (files) {
files.filter(function (file) {
if(this._checkFileSize(file)) {
alert(
this._translate('Maximum allowed file size for upload is') +
" " + this.miscConfig.maxSizePlural + "\n" +
this._translate('Please check your server PHP settings.')
);
file.cancel();
return false;
}
return true;
}.bind(this)).each(function (file) {
this._handleUpdateFile(file);
}.bind(this));
var i = 0;
(function($){
$('.uploader #-container').each( function () {
$(this).attr( 'id', files[i].uniqueIdentifier + '-container' );
$(this).find( '.file-info' ).text( files[i].name );
i++;
});
})(jQuery)
},
When I try to upload an image in Magento 1.8.1.0, I can browse and choose an image to upload, but after selecting one the bar that would normally have the file name is blank. Clicking the remove, or upload file buttons afterward results in console errors, such as "Uncaught TypeError: file.cancel is not a function". I'm guessing this is because the file isn't getting populated after browsing and selecting an image to begin with.
I have spent time browsing other peoples issues with the image up-loader in this and different Magento versions, but have not found someone with this issue. It is a site that recently changed servers, but I unfortunately don't know if this was a previous issue, or a result of it changing servers. Any help is appreciated and if I missed someone else's question that matches this I apologize. Below are screenshots.
file details not showing after selecting an image
js console error example
Here is the code from my instance.js file, I turned on debugging and when I browse and select a file, it is console loggin my file details but it is showing like in the provided screenshot (blank)
(function(flowFactory, window, document) {
'use strict';
window.Uploader = Class.create({
/**
* #type {Boolean} Are we in debug mode?
*/
debug: true,
/**
* #constant
* #type {String} templatePattern
*/
templatePattern: /(^|.|\r|\n)({{(\w+)}})/,
/**
* #type {JSON} Array of elements ids to instantiate DOM collection
*/
elementsIds: [],
/**
* #type {Array.<HTMLElement>} List of elements ids across all uploader functionality
*/
elements: [],
/**
* #type {(FustyFlow|Flow)} Uploader object instance
*/
uploader: {},
/**
* #type {JSON} General Uploader config
*/
uploaderConfig: {},
/**
* #type {JSON} browseConfig General Uploader config
*/
browseConfig: {},
/**
* #type {JSON} Misc settings to manipulate Uploader
*/
miscConfig: {},
/**
* #type {Array.<String>} Sizes in plural
*/
sizesPlural: ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
/**
* #type {Number} Precision of calculation during convetion to human readable size format
*/
sizePrecisionDefault: 3,
/**
* #type {Number} Unit type conversion kib or kb, etc
*/
sizeUnitType: 1024,
/**
* #type {String} Default delete button selector
*/
deleteButtonSelector: '.delete',
/**
* #type {Number} Timeout of completion handler
*/
onCompleteTimeout: 1000,
/**
* #type {(null|Array.<FlowFile>)} Files array stored for success event
*/
files: null,
/**
* #name Uploader
*
* #param {JSON} config
*
* #constructor
*/
initialize: function(config) {
this.elementsIds = config.elementIds;
this.elements = this.getElements(this.elementsIds);
this.uploaderConfig = config.uploaderConfig;
this.browseConfig = config.browseConfig;
this.miscConfig = config.miscConfig;
this.uploader = flowFactory(this.uploaderConfig);
this.attachEvents();
/**
* Bridging functions to retain functionality of existing modules
*/
this.formatSize = this._getPluralSize.bind(this);
this.upload = this.onUploadClick.bind(this);
this.onContainerHideBefore = this.onTabChange.bind(this);
},
/**
* Array of strings containing elements ids
*
* #param {JSON.<string, Array.<string>>} ids as JSON map,
* {<type> => ['id1', 'id2'...], <type2>...}
* #returns {Array.<HTMLElement>} An array of DOM elements
*/
getElements: function (ids) {
/** #type {Hash} idsHash */
var idsHash = $H(ids);
idsHash.each(function (id) {
var result = this.getElementsByIds(id.value);
idsHash.set(id.key, result);
}.bind(this));
return idsHash.toObject();
},
/**
* Get HTMLElement from hash values
*
* #param {(Array|String)}ids
* #returns {(Array.<HTMLElement>|HTMLElement)}
*/
getElementsByIds: function (ids) {
var result = [];
if(ids && Object.isArray(ids)) {
ids.each(function(fromId) {
var DOMElement = $(fromId);
if (DOMElement) {
// Add it only if it's valid HTMLElement, otherwise skip.
result.push(DOMElement);
}
});
} else {
result = $(ids)
}
return result;
},
/**
* Attach all types of events
*/
attachEvents: function() {
this.assignBrowse();
this.uploader.on('filesSubmitted', this.onFilesSubmitted.bind(this));
this.uploader.on('uploadStart', this.onUploadStart.bind(this));
this.uploader.on('fileSuccess', this.onFileSuccess.bind(this));
this.uploader.on('complete', this.onSuccess.bind(this));
if(this.elements.container && !this.elements.delete) {
this.elements.container.on('click', this.deleteButtonSelector, this.onDeleteClick.bind(this));
} else {
if(this.elements.delete) {
this.elements.delete.on('click', Event.fire.bind(this, document, 'upload:simulateDelete', {
containerId: this.elementsIds.container
}));
}
}
if(this.elements.upload) {
this.elements.upload.invoke('on', 'click', this.onUploadClick.bind(this));
}
if(this.debug) {
this.uploader.on('catchAll', this.onCatchAll.bind(this));
}
},
onTabChange: function (successFunc) {
if(this.uploader.files.length && !Object.isArray(this.files)) {
if(confirm(
this._translate('There are files that were selected but not uploaded yet. After switching to another tab your selections will be lost. Do you wish to continue ?')
)
) {
if(Object.isFunction(successFunc)) {
successFunc();
} else {
this._handleDelete(this.uploader.files);
document.fire('uploader:fileError', {
containerId: this.elementsIds.container
});
}
} else {
return 'cannotchange';
}
}
},
/**
* Assign browse buttons to appropriate targets
*/
assignBrowse: function() {
if (this.elements.browse && this.elements.browse.length) {
this.uploader.assignBrowse(
this.elements.browse,
this.browseConfig.isDirectory || false,
this.browseConfig.singleFile || false,
this.browseConfig.attributes || {}
);
}
},
/**
* #event
* #param {Array.<FlowFile>} files
*/
onFilesSubmitted: function (files) {
files.filter(function (file) {
if(this._checkFileSize(file)) {
alert(
this._translate('Maximum allowed file size for upload is') +
" " + this.miscConfig.maxSizePlural + "\n" +
this._translate('Please check your server PHP settings.')
);
file.cancel();
return false;
}
return true;
}.bind(this)).each(function (file) {
this._handleUpdateFile(file);
}.bind(this));
},
_handleUpdateFile: function (file) {
var replaceBrowseWithRemove = this.miscConfig.replaceBrowseWithRemove;
if(replaceBrowseWithRemove) {
document.fire('uploader:simulateNewUpload', { containerId: this.elementsIds.container });
}
this.elements.container
[replaceBrowseWithRemove ? 'update':'insert'](this._renderFromTemplate(
this.elements.templateFile,
{
name: file.name,
size: file.size ? '(' + this._getPluralSize(file.size) + ')' : '',
id: file.uniqueIdentifier
}
)
);
},
/**
* Upload button is being pressed
*
* #event
*/
onUploadStart: function () {
var files = this.uploader.files;
files.each(function (file) {
var id = file.uniqueIdentifier;
this._getFileContainerById(id)
.removeClassName('new')
.removeClassName('error')
.addClassName('progress');
this._getProgressTextById(id).update(this._translate('Uploading...'));
var deleteButton = this._getDeleteButtonById(id);
if(deleteButton) {
this._getDeleteButtonById(id).hide();
}
}.bind(this));
this.files = this.uploader.files;
},
/**
* Get file-line container by id
*
* #param {String} id
* #returns {HTMLElement}
* #private
*/
_getFileContainerById: function (id) {
return $(id + '-container');
},
/**
* Get text update container
*
* #param id
* #returns {*}
* #private
*/
_getProgressTextById: function (id) {
return this._getFileContainerById(id).down('.progress-text');
},
_getDeleteButtonById: function(id) {
return this._getFileContainerById(id).down('.delete');
},
/**
* Handle delete button click
*
* #event
* #param {Event} e
*/
onDeleteClick: function (e) {
var element = Event.findElement(e);
var id = element.id;
if(!id) {
id = element.up(this.deleteButtonSelector).id;
}
this._handleDelete([this.uploader.getFromUniqueIdentifier(id)]);
},
/**
* Complete handler of uploading process
*
* #event
*/
onSuccess: function () {
document.fire('uploader:success', { files: this.files });
this.files = null;
},
/**
* Successfully uploaded file, notify about that other components, handle deletion from queue
*
* #param {FlowFile} file
* #param {JSON} response
*/
onFileSuccess: function (file, response) {
response = response.evalJSON();
var id = file.uniqueIdentifier;
var error = response.error;
this._getFileContainerById(id)
.removeClassName('progress')
.addClassName(error ? 'error': 'complete')
;
this._getProgressTextById(id).update(this._translate(
error ? this._XSSFilter(error) :'Complete'
));
setTimeout(function() {
if(!error) {
document.fire('uploader:fileSuccess', {
response: Object.toJSON(response),
containerId: this.elementsIds.container
});
} else {
document.fire('uploader:fileError', {
containerId: this.elementsIds.container
});
}
this._handleDelete([file]);
}.bind(this) , !error ? this.onCompleteTimeout: this.onCompleteTimeout * 3);
},
/**
* Upload button click event
*
* #event
*/
onUploadClick: function () {
try {
this.uploader.upload();
} catch(e) {
if(console) {
console.error(e);
}
}
},
/**
* Event for debugging purposes
*
* #event
*/
onCatchAll: function () {
if(console.group && console.groupEnd && console.trace) {
var args = [].splice.call(arguments, 1);
console.group();
console.info(arguments[0]);
console.log("Uploader Instance:", this);
console.log("Event Arguments:", args);
console.trace();
console.groupEnd();
} else {
console.log(this, arguments);
}
},
/**
* Handle deletition of files
* #param {Array.<FlowFile>} files
* #private
*/
_handleDelete: function (files) {
files.each(function (file) {
file.cancel();
var container = $(file.uniqueIdentifier + '-container');
if(container) {
container.remove();
}
}.bind(this));
},
/**
* Check whenever file size exceeded permitted amount
*
* #param {FlowFile} file
* #returns {boolean}
* #private
*/
_checkFileSize: function (file) {
return file.size > this.miscConfig.maxSizeInBytes;
},
/**
* Make a translation of string
*
* #param {String} text
* #returns {String}
* #private
*/
_translate: function (text) {
try {
return Translator.translate(text);
}
catch(e){
return text;
}
},
/**
* Render from given template and given variables to assign
*
* #param {HTMLElement} template
* #param {JSON} vars
* #returns {String}
* #private
*/
_renderFromTemplate: function (template, vars) {
var t = new Template(this._XSSFilter(template.innerHTML), this.templatePattern);
return t.evaluate(vars);
},
/**
* Format size with precision
*
* #param {Number} sizeInBytes
* #param {Number} [precision]
* #returns {String}
* #private
*/
_getPluralSize: function (sizeInBytes, precision) {
if(sizeInBytes == 0) {
return 0 + this.sizesPlural[0];
}
var dm = (precision || this.sizePrecisionDefault) + 1;
var i = Math.floor(Math.log(sizeInBytes) / Math.log(this.sizeUnitType));
return (sizeInBytes / Math.pow(this.sizeUnitType, i)).toPrecision(dm) + ' ' + this.sizesPlural[i];
},
/**
* Purify template string to prevent XSS attacks
*
* #param {String} str
* #returns {String}
* #private
*/
_XSSFilter: function (str) {
return str
.stripScripts()
// Remove inline event handlers like onclick, onload, etc
.replace(/(on[a-z]+=["][^"]+["])(?=[^>]*>)/img, '')
.replace(/(on[a-z]+=['][^']+['])(?=[^>]*>)/img, '')
;
}
});
})(fustyFlowFactory, window, document);
Update: Running through the js doing a bunch of logging, looks like the file container isn't being created correctly, so I am looking into that atm
Figured this out, posted the solution up above, but I changed the onFilesSubmitted function (it wasn't creating the file containers) to this:
onFilesSubmitted: function (files) {
files.filter(function (file) {
if(this._checkFileSize(file)) {
alert(
this._translate('Maximum allowed file size for upload is') +
" " + this.miscConfig.maxSizePlural + "\n" +
this._translate('Please check your server PHP settings.')
);
file.cancel();
return false;
}
return true;
}.bind(this)).each(function (file) {
this._handleUpdateFile(file);
}.bind(this));
var i = 0;
(function($){
$('.uploader #-container').each( function () {
$(this).attr( 'id', files[i].uniqueIdentifier + '-container' );
$(this).find( '.file-info' ).text( files[i].name );
i++;
});
})(jQuery)
},

How should I create a Tree structure in Rally of defects with respect to user story

I am able to get tree structure for the user stories but want it same for defects also which are related to particular user story so that at a singe screen I can see both user stories and the related defects.
You may use features: [{ftype:'groupingsummary'}] of ExtJS to group defects by user stories and even summarize by some other field, in the code below by PlanEstimate. To group defects by user story Requirement attribute on defect is used, which points to the related story. In this example defects are filtered by Iteration.
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
comboboxConfig: {
fieldLabel: 'Select Iteration:',
labelWidth: 100
},
onScopeChange: function() {
this.makeStore();
},
makeStore: function() {
var filter = Ext.create('Rally.data.wsapi.Filter', {
property: 'Requirement',
operator: '!=',
value: null
});
filter= filter.and(this.getContext().getTimeboxScope().getQueryFilter());
filter.toString();
Ext.create('Rally.data.wsapi.Store', {
model: 'Defect',
fetch: ['ObjectID', 'FormattedID', 'Name', 'State', 'Requirement', 'PlanEstimate'],
autoLoad: true,
filters: [filter],
listeners: {
load: this.onDataLoaded,
scope: this
}
});
},
onDataLoaded: function(store, records){
if (records.length === 0) {
this.notifyNoDefects();
}
else{
if (this.notifier) {
this.notifier.destroy();
}
var that = this;
var promises = [];
_.each(records, function(defect) {
promises.push(this.getStory(defect, this));
},this);
Deft.Promise.all(promises).then({
success: function(results) {
that.defects = results;
that.makeGrid();
}
});
}
},
getStory: function(defect, scope) {
var deferred = Ext.create('Deft.Deferred');
var that = scope;
var storyOid = defect.get('Requirement').ObjectID;
Rally.data.ModelFactory.getModel({
type: 'HierarchicalRequirement',
scope: this,
success: function(model, operation) {
fetch: ['FormattedID','ScheduleState'],
model.load(storyOid, {
scope: this,
success: function(record, operation) {
var storyScheduleState = record.get('ScheduleState');
var storyFid = record.get('FormattedID');
var defectRef = defect.get('_ref');
var defectOid = defect.get('ObjectID');
var defectFid = defect.get('FormattedID');
var defectPlanEstimate = defect.get('PlanEstimate');
var defectName = defect.get('Name');
var defectState = defect.get('State');
var story = defect.get('Requirement');
result = {
"_ref" : defectRef,
"ObjectID" : defectOid,
"FormattedID" : defectFid,
"Name" : defectName,
"PlanEstimate" : defectPlanEstimate,
"State" : defectState,
"Requirement" : story,
"StoryState" : storyScheduleState,
"StoryID" : storyFid
};
deferred.resolve(result);
}
});
}
});
return deferred;
},
makeGrid: function() {
var that = this;
if (this.grid) {
this.grid.destroy();
}
var gridStore = Ext.create('Rally.data.custom.Store', {
data: that.defects,
groupField: 'StoryID',
pageSize: 1000,
});
this.grid = Ext.create('Rally.ui.grid.Grid', {
itemId: 'defectGrid',
store: gridStore,
features: [{ftype:'groupingsummary'}],
minHeight: 500,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name',
},
{
text: 'State', dataIndex: 'State',
summaryRenderer: function() {
return "PlanEstimate Total";
}
},
{
text: 'PlanEstimate', dataIndex: 'PlanEstimate',
summaryType: 'sum'
},
{
text: 'Story', dataIndex: 'Story',
renderer: function(val, meta, record) {
return '' + record.get('Requirement').FormattedID + '';
}
},
{
text: 'Story Schedule State', dataIndex: 'StoryState',
}
]
});
this.add(this.grid);
this.grid.reconfigure(gridStore);
},
notifyNoDefects: function() {
if (this.grid) {
this.grid.destroy();
}
if (this.notifier) {
this.notifier.destroy();
}
this.notifier = Ext.create('Ext.Container',{
xtype: 'container',
itemId: 'notifyContainer',
html: "No Defects found matching selection."
});
this.add( this.notifier);
}
});

A marker can only be added to a layer of type "group" error even though type "group" is defined

I am having trouble with an error that seems obvious but I've tried everything. I must be missing something. I'm running a rails app with Angularjs and angular-leaflet-directives. I am trying to add markers to a specific group. No matter what I do, I keep getting this error:
[AngularJS - Leaflet] A marker can only be added to a layer of type "group"
I know that this error comes up when the overlays type isn't group. The issue is that in my case, it is!
EDIT: here is a plunkr where I recreated the bug: http://plnkr.co/edit/DLCN5RYVr0BheYzTuqkQ?p=preview
Here is my code:
assets/javascript/angular/services/service.js
app.factory('Markers', ["$http", "$q", function($http, $q) {
var Markers = []
var events_markers = []
var stories_markers = []
var defaultIcon = L.icon({
iconUrl: 'assets/dot-grey.png',
iconSize: [15, 15],
iconAnchor: [15, 15],
popupAnchor: [-7, -20]
})
// Getting events + stories from rails API
var event_data = $http.get("/api/v1/events.json"),
story_data = $http.get("/api/v1/stories.json")
// Setting event markers and stories markers
$q.all([event_data, story_data]).then(function(results) {
var data_stories = results[1].data.stories
var data_events = results[0].data.event
for (i=0 ; i < data_stories.length; i++){
for (j=0; j < data_stories[i].locations.length; j++){
var lat = data_stories[i].locations[j].latitude
var lng = data_stories[i].locations[j].longitude
var title = data_stories[i].title
var layer = "stories"
Markers.push({layer: layer, lat:lat, lng:lng, message: title, icon: defaultIcon})
}
}
for (e=0 ; e < data_events.length; e++){
if (data_events[e].latitude != null){
var lat = data_events[e].latitude
var lng = data_events[e].longitude
var title = data_events[e].name
var layer = "events"
Markers.push({layer: layer, lat:lat, lng: lng, message: title, icon: defaultIcon})
}
}
return Markers
});
return {
markers: Markers
}
}]);
assets/javascript/angular/controllers/MapCtrl.js
app.controller("MapCtrl", ['$scope', "$timeout", "leafletData", "Markers", function($scope,
$timeout, leafletData, Markers ){
$scope.isVisible = true;
$scope.markers = Markers.markers;
var bounds = {
northEast:{
lat: 37.86862005954327,
lng: -122.12230682373048
},
southWest:{
lat: 37.68436373334184,
lng: -122.55901336669923
}
}
function setMap($scope, markers, bounds) {
angular.extend($scope, {
maxbounds: bounds,
defaults: {
scrollWheelZoom: false,
maxZoom: 14,
minZoom: 10
},
layers: {
baselayers: {
mapbox:{
name: 'Mapbox Litography',
url: 'http://api.tiles.mapbox.com/v4/{mapid}/{z}/{x}/{y}.png?access_token={apikey}',
type: 'xyz',
layerParams: {
apikey: 'pk.eyJ1IjoibGF1cmVuYmVuaWNob3UiLCJhIjoiQ1BlZGczRSJ9.EVMieITn7lHNi6Ato9wFwg',
mapid: 'laurenbenichou.jm96meb6'
}
}
}
},
overlays: {
stories: {
type: 'group', //Note here that the type is indeed 'group'
name: 'stories',
visible: true
},
events: {
type: 'group',
name: 'events',
visible: false
}
},
markers: markers
});
}
setMap($scope, $scope.markers, bounds)
angular.element(document).ready(function () {
function toggle(){$scope.isVisible = !$scope.isVisible;}
$timeout(toggle, 1000);
});
}])
assets/javascript/templates/map.html
<leaflet ng-hide="isVisible" defaults="defaults" markers="markers" layers="layers" height="100%" width="100%" maxbounds="maxbounds" ></leaflet>
assets/javascript/angular/app.js
var app = angular.module("litography", ['ngAnimate','ui.router','ngResource', 'templates', 'leaflet-directive', 'cn.offCanvas', 'ui.bootstrap', 'angular-flexslider'])
.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', function($stateProvider, $urlRouterProvider, $locationProvider) {
/**
* Routes and States
*/
$stateProvider
.state('home', {
url: "/",
views:{
"splash": {
templateUrl: "splash.html",
controller: "SplashCtrl"
},
"map":{
templateUrl: "map.html",
controller: "MapCtrl",
resolve:{
Markers: function(Markers){
return Markers
}
}
},
"menu":{
templateUrl: "menu.html",
controller: "MenuCtrl"
}
}
})
// default fall back route
$urlRouterProvider.otherwise('/');
// enable HTML5 Mode for SEO
$locationProvider.html5Mode(true);
}]);
It can happen if you forgot to add the file: leaflet.markercluster.js
Ok, from the reduced plunker it was easier to play around and find the bug.
You need to put overlays inside (i.e. as a property of) layers:
layers: {
baselayers: {
mapbox: {
name: 'mapbox',
url: 'http://api.tiles.mapbox.com/v4/{mapid}/{z}/{x}/{y}.png?access_token={apikey}',
type: 'xyz',
layerParams: {
apikey: 'pk.eyJ1IjoibGF1cmVuYmVuaWNob3UiLCJhIjoiQ1BlZGczRSJ9.EVMieITn7lHNi6Ato9wFwg',
mapid: 'laurenbenichou.jm96meb6',
name: "stories"
}
}
},
overlays: {
stories: {
type: 'group',
name: 'stories',
visible: true,
},
events: {
type: 'group',
name: 'events',
visible: false
}
}
}

Resources