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?
Related
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"
}]
};
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)
},
I'm having a really difficult time connecting admin-on-rest with my rails api. I've been following the tutorial walk through at https://marmelab.com/admin-on-rest/Tutorial.html but when I point to my localhost is where all the trouble starts.
Response
{
"events": [
{
"id": 13,
"name": "Event 1",
"description": "test"
},
{
"id": 16,
"name": "Event 2",
"description": "dsadfa adf asd"
},
{
"id": 17,
"name": "Event 3",
"description": "Hey this is a test"
},
{
"id": 18,
"name": "Some Stuff",
"description": "Yay, it work"
},
{
"id": 20,
"name": "Test",
"description": "asdfs"
}
],
"meta": {
"current_page": 1,
"next_page": null,
"prev_page": null,
"total_pages": 1,
"total_count": 5
}
}
App.js
import React from 'react';
import { jsonServerRestClient, fetchUtils, Admin, Resource } from 'admin-on-rest';
import apiClient from './apiClient';
import { EventList } from './events';
import { PostList } from './posts';
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
// add your own headers here
// options.headers.set('X-Custom-Header', 'foobar');
return fetchUtils.fetchJson(url, options);
}
const restClient = apiClient('http://localhost:5000/admin', httpClient);
const App = () => (
<Admin title="My Admin" restClient={restClient}>
<Resource name="events" list={EventList} />
</Admin>
);
export default App;
apiClient.js
import { stringify } from 'query-string';
import {
GET_LIST,
GET_ONE,
GET_MANY,
GET_MANY_REFERENCE,
CREATE,
UPDATE,
DELETE,
fetchJson
} from 'admin-on-rest';
/**
* Maps admin-on-rest queries to a simple REST API
*
* The REST dialect is similar to the one of FakeRest
* #see https://github.com/marmelab/FakeRest
* #example
* GET_LIST => GET http://my.api.url/posts?sort=['title','ASC']&range=[0, 24]
* GET_ONE => GET http://my.api.url/posts/123
* GET_MANY => GET http://my.api.url/posts?filter={ids:[123,456,789]}
* UPDATE => PUT http://my.api.url/posts/123
* CREATE => POST http://my.api.url/posts/123
* DELETE => DELETE http://my.api.url/posts/123
*/
export default (apiUrl, httpClient = fetchJson) => {
/**
* #param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE'
* #param {String} resource Name of the resource to fetch, e.g. 'posts'
* #param {Object} params The REST request params, depending on the type
* #returns {Object} { url, options } The HTTP request parameters
*/
const convertRESTRequestToHTTP = (type, resource, params) => {
console.log(type)
console.log(params)
console.log(params.filter)
console.log(resource)
console.log(fetchJson)
let url = '';
const options = {};
switch (type) {
case GET_LIST: {
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const query = {
sort: JSON.stringify([field, order]),
range: JSON.stringify([
(page - 1) * perPage,
page * perPage - 1,
]),
filter: JSON.stringify(params.filter),
};
url = `${apiUrl}/${resource}?${stringify(query)}`;
break;
}
case GET_ONE:
url = `${apiUrl}/${resource}/${params.id}`;
break;
case GET_MANY: {
const query = {
filter: JSON.stringify({ id: params.ids }),
};
url = `${apiUrl}/${resource}?${stringify(query)}`;
break;
}
case GET_MANY_REFERENCE: {
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const query = {
sort: JSON.stringify([field, order]),
range: JSON.stringify([
(page - 1) * perPage,
page * perPage - 1,
]),
filter: JSON.stringify({
...params.filter,
[params.target]: params.id,
}),
};
url = `${apiUrl}/${resource}?${stringify(query)}`;
break;
}
case UPDATE:
url = `${apiUrl}/${resource}/${params.id}`;
options.method = 'PUT';
options.body = JSON.stringify(params.data);
break;
case CREATE:
url = `${apiUrl}/${resource}`;
options.method = 'POST';
options.body = JSON.stringify(params.data);
break;
case DELETE:
url = `${apiUrl}/${resource}/${params.id}`;
options.method = 'DELETE';
break;
default:
throw new Error(`Unsupported fetch action type ${type}`);
}
return { url, options };
};
/**
* #param {Object} response HTTP response from fetch()
* #param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE'
* #param {String} resource Name of the resource to fetch, e.g. 'posts'
* #param {Object} params The REST request params, depending on the type
* #returns {Object} REST response
*/
const convertHTTPResponseToREST = (response, type, resource, params) => {
const { headers, json } = response;
switch (type) {
case GET_LIST:
case GET_MANY_REFERENCE:
// if (!headers.has('content-range')) {
// throw new Error(
// 'The Content-Range header is missing in the HTTP Response. The simple REST client expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?'
// );
// }
console.log("DATA", json[resource])
return {
data: json[resource],
total: json.meta.total_count
};
case CREATE:
return { data: { ...params.data, id: json.id } };
default:
return { data: json };
}
};
/**
* #param {string} type Request type, e.g GET_LIST
* #param {string} resource Resource name, e.g. "posts"
* #param {Object} payload Request parameters. Depends on the request type
* #returns {Promise} the Promise for a REST response
*/
return (type, resource, params) => {
const { url, options } = convertRESTRequestToHTTP(
type,
resource,
params
);
return httpClient(url, options).then(response =>{
console.log("RESPONSE", response);
convertHTTPResponseToREST(response, type, resource, params)}
);
};
};
events.js
import React from 'react';
import { List, Datagrid, Edit, Create, SimpleForm, DateField, ImageField, ReferenceField, translate,
TextField, EditButton, DisabledInput, TextInput, LongTextInput, DateInput, Show, Tab, TabbedShowLayout } from 'admin-on-rest';
export EventIcon from 'material-ui/svg-icons/action/today';
const EventTitle = translate(({ record, translate }) => (
<span>
{record ? translate('event.edit.name', { title: record.name }) : ''}
</span>
));
export const EventList = (props) => (
<List {...props}>
<Datagrid>
<TextField source="id" />
<TextField source="name" />
<TextField source="description" />
<DateField source="date" />
<ImageField source="flyer" />
<EditButton basePath="/events" />
</Datagrid>
</List>
);
The error i receive current is Cannot read property 'data' of undefined but I can verify through my logs that the data is being received correctly.
Try using data as the root level key in your json response. Change events to data, for example:
{
"data": [
{
"id": 13,
"name": "Event 1",
"description": "test"
},
{
"id": 16,
"name": "Event 2",
"description": "dsadfa adf asd"
}
],
"meta": {...}
}
suggestion and code sample
I am new to Backbone marionette, I have a view ("JoinCommunityNonmemberWidgetview.js") which opens a modal dialog ("JoinCommunityDetailWidgetview.js").On closing of the dialog ( I want the view JoinCommunityNonmemberWidgetview.js") to be refreshed again by calling a specific function "submitsuccess" of the view JoinCommunityNonmemberWidgetview.js.
How can I achieve it.
The code for the modal is as below:
define(
[
"grads",
"views/base/forms/BaseFormLayout",
"models/MembershipRequestModel",
"require.text!templates/communitypagewidget/JoinCommunityWidgetDetailTemplate.htm",
],
function (grads, BaseFormLayout, MembershipRequestModel, JoinCommunityWidgetDetailTemplate) {
// Create custom bindings for edit form
var MemberDetailbindings = {
'[name="firstname"]': 'FirstName',
'[name="lastname"]': 'LastName',
'[name="organization"]': 'InstitutionName',
'[name="email"]': 'Email'
};
var Detailview = BaseFormLayout.extend({
formViewOptions: {
template: JoinCommunityWidgetDetailTemplate,
bindings: MemberDetailbindings,
labels: {
'InstitutionName': "Organization"
},
validation: {
'Email': function (value) {
var emailconf = this.attributes.conf;
if (value != emailconf) {
return 'Email message and Confirm email meassage should match';
}
}
}
},
editViewOptions: {
viewEvents: {
"after:render": function () {
var self = this;
var btn = this.$el.find('#buttonSubmit');
$j(btn).button();
}
}
},
showToolbar: false,
editMode: true,
events: {
"click [data-name='buttonSubmit']": "handleSubmitButton"
},
beforeInitialize: function (options) {
this.model = new MembershipRequestModel({ CommunityId: this.options.communityId, MembershipRequestStatusTypeId: 1, RequestDate: new Date() });
},
onRender: function () {
BaseFormLayout.prototype.onRender.call(this)
},
handleSubmitButton: function (event) {
this.hideErrors();
// this.model.set({ conf: 'conf' });
this.model.set({ conf: this.$el.find('#confirmemail-textbox').val() });
//this.form.currentView.save();
//console.log(this.form);
this.model.save({}, {
success: this.saveSuccess.bind(this),
error: this.saveError.bind(this),
wait: true
});
},
saveSuccess: function (model, response) {
var mesg = 'You have submitted a request to join this community.';
$j('<div>').html(mesg).dialog({
title: 'Success',
buttons: {
OK: function () {
$j(this).dialog('close');
}
}
});
grads.modal.close();
},
saveError: function (model, response) {
var msg = 'There was a problem. The request could not be processed.Please try again.';
$j('<div>').html(msg).dialog({
title: 'Error',
buttons: {
OK: function () {
$j(this).dialog('close');
}
}
});
}
});
return Detailview;
}
);
I would use Marionette's event framework.
Take a look at: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.commands.md
Specifically, you need to:
1) Create a marionette application :
App = new Marionette.Application();
2) Use the application to set up event handlers
//Should be somewhere you can perform the logic you are after
App.commands.setHandler('refresh');
3) Fire a 'command' and let marionette route the event
App.execute('refresh');
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
});
}
}