Request the data form API fail - async-await

I created a code to request data from API to use the data to make a flash card
const BASE_API_URL = "https://jservice.io/api/";
const NUM_CATEGORIES = 6;
const NUM_CLUES_PER_CAT = 5;
// categories is the main data structure for the app; it looks like this:
// [
// { title: "Math",
// clues: [
// {question: "2+2", answer: 4, showing: null},
// {question: "1+1", answer: 2, showing: null}
// ...
// ],
// },
// { title: "Literature",
// clues: [
// {question: "Hamlet Author", answer: "Shakespeare", showing: null},
// {question: "Bell Jar Author", answer: "Plath", showing: null},
// ...
// ],
// },
// ...
// ]
let categories = [];
/** Get NUM_CATEGORIES random category from API.
*
* Returns array of category ids
*/
async function getCategoryIds() {
let response = await axios.get(`${BASE_API_URL}categories?count=100`);
let catIds = response.data.map(c => c.id);
return _.sampleSize(catIds, NUM_CATEGORIES)
};
/** Return object with data about a category:
*
* Returns { title: "Math", clues: clue-array }
*
* Where clue-array is:
* [
* {question: "Hamlet Author", answer: "Shakespeare", showing: null},
* {question: "Bell Jar Author", answer: "Plath", showing: null},
* ...
* ]
*/
async function getCategory(catId) {
let response = await axios.get(`${BASE_API_URL}categories?id=${catId}`);
let cat = response.data;
let allClues = cat.clues;
let randomClues = _.sampleSize(allClues, NUM_CLUES_PER_CAT);
let clues = randomClues.map(c => ({
question: c.question,
answer: c.answer,
showing: null,
}));
return {
title: cat.title,
clues
};
}
/** Fill the HTML table#jeopardy with the categories & cells for questions.
*
* - The <thead> should be filled w/a <tr>, and a <td> for each category
* - The <tbody> should be filled w/NUM_QUESTIONS_PER_CAT <tr>s,
* each with a question for each category in a <td>
* (initally, just show a "?" where the question/answer would go.)
*/
async function fillTable() {
$("#jeopardy thead").empty();
let $tr = $("<tr>");
for (let catIdx = 0; catIdx < NUM_CATEGORIES; catIdx++) {
$tr.append($("<th>").text(categories[catIdx].title));
}
$("#jeopardy thead").append($tr);
// add rows for question of each categories
$("#jeopardy tbody").empty();
for (let clueIdx = 0; clueIdx < NUM_CLUES_PER_CAT; clueIdx++) {
let $tr = $("<tr>");
for (let catIdx = 0; catIdx < NUM_CATEGORIES; catIdx++) {
$tr.append($("<td>").attr("id", `${catIdx}-${clueIdx}`).text("?"));
}
$("#jeopardy tbody").append($tr);
}
}
/** Handle clicking on a clue: show the question or answer.
*
* Uses .showing property on clue to determine what to show:
* - if currently null, show question & set .showing to "question"
* - if currently "question", show answer & set .showing to "answer"
* - if currently "answer", ignore click
* */
function handleClick(evt) {
let id = evt.target.id;
let [catId, clueId] = id.split("-");
let clue = categories[catId].clues[clueId];
let msg;
if (!clue.showing) {
msg = clue.question;
clue.showing = "question";
} else if (clue.showing === "question") {
msg = clue.answer;
clue.showing = "answer";
} else {
return
}
// Update text
$(`#${catIdx}-${clueId}`.html(msg));
}
/** Wipe the current Jeopardy board, show the loading spinner,
* and update the button used to fetch data.
*/
function showLoadingView() {
}
/** Remove the loading spinner and update the button used to fetch data. */
function hideLoadingView() {}
/** Start game:
*
* - get random category Ids
* - get data for each category
* - create HTML table
* */
async function setupAndStart() {
let catIds = await getCategoryIds();
categories = [];
for (let catId of catIds) {
categories.push(await getCategory(catId));
}
fillTable();
}
/** On click of start / restart button, set up game. */
$("#restart").on("click", setupAndStart);
/** On page load, add event handler for clicking clues */
$(async function() {
setupAndStart();
$("#jeopardy").on("click", "td", handleClick);
})
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Jeopardy</title>
<link href="https://fonts.googleapis.com/css?family=Copse&display=swap" rel="stylesheet">
<link rel="stylesheet" href="jeopardy.css">
</head>
<div class="container">
<h1>JEOPARDY</h1>
<table id="jeopardy">
<thead>
</thead>
<tbody>
</tbody>
</table>
<button id="restart">Restart Game</button>
</div>
<script src="https://unpkg.com/jquery"></script>
<script src="https://unpkg.com/axios/dist/axios.js"></script>
<script src="https://unpkg.com/lodash"></script>
<script src="jeopardy.js"></script>
I just make a form that can get data form the BASE API URL to get the data
and send data to the table that I create but the data isn't appear.
I used develop tools on google chrome to tested out but it said undefine.
I have no idea what is that code problem. I need some help top figure out the problem to fix.
BASE_API_URL

Related

next js with ssr enabled: Og image not displaying while sharing using linkedln

I am forming og image(s3 link) based on id send as query params.
In app.js, inside head, I am assigning og:image content value to s3 link based on router path.
const { query } = useRouter();
const { id } = query;
<Head>
{router.pathname === "/certificate/course-complete" &&
<meta
property="og:image"
content={`${process.env.CERTIFICATE_S3_ORIGIN}/course-complete/${id}.jpg`}
/>
}
</Head>
Now, when i inspect the page, i look for og:image meta, its present and its valus is s3 url. But, its not read by https://www.linkedin.com/post-inspector/ or https://socialsharepreview.com/. they are not getting those og:image from the url.
If i hard code the s3 link, then linkedln inspector is getting the og:image and its working fine.
<Head>
<meta
property="og:image"
content={`${process.env.CERTIFICATE_S3_ORIGIN}/course-complete/199111.jpg`}
/>
</Head>
Please help how can i generate dynamic og:image(s3 link formed using id as query params from the url) for a page in next js
getInitialProps used in _document.js
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
in netlify, the following is used to run the application.
next build and next export
code to set meta tags
import { Col, Row } from 'antd';
import { NextSeo } from 'next-seo';
export async function getServerSideProps(context) {
const { id } = context.query;
return {
props: {id: id}
}
}
const Certificate = (props) => {
return (
<>
<NextSeo
canonical={`${process.env.CERTIFICATE_URL}/?id=${props.id}`}
openGraph={{
url: `${process.env.CERTIFICATE_S3_ORIGIN}/`,
images: [
{
url: `${process.env.CERTIFICATE_S3_ORIGIN}/${props.id}.jpg`,
width: 800,
height: 600,
type: 'image/jpeg',
}
],
}}
/>
// code here
</>
);
};
export default Certificate;

Implement Aria 1.1

I am trying to implement the example 1 provided by w3c.org. The URL is https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html. It keeps giving me aria undefined error on line (var ex1Combobox = new aria.ListboxCombobox). Here is the code:
<!DOCTYPE html>
<html>
<body>
<label for="ex1-input"
id="ex1-label"
class="combobox-label">
Choice 1 Fruit or Vegetable
</label>
<div class="combobox-wrapper">
<div role="combobox"
aria-expanded="false"
aria-owns="ex1-listbox"
aria-haspopup="listbox"
id="ex1-combobox">
<input type="text"
aria-autocomplete="list"
aria-controls="ex1-listbox"
id="ex1-input">
</div>
<ul aria-labelledby="ex1-label"
role="listbox"
id="ex1-listbox"
class="listbox hidden">
</ul>
</div>
<script>
/*
* This content is licensed according to the W3C Software License at
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*
* ARIA Combobox Examples
*/
var FRUITS_AND_VEGGIES = [
'Apple',
'Artichoke',
'Asparagus',
'Banana',
'Beets',
'Bell pepper',
'Broccoli',
'Brussels sprout',
'Cabbage',
'Carrot',
'Cauliflower',
'Celery',
'Chard',
'Chicory',
'Corn',
'Cucumber',
'Daikon',
'Date',
'Edamame',
'Eggplant',
'Elderberry',
'Fennel',
'Fig',
'Garlic',
'Grape',
'Honeydew melon',
'Iceberg lettuce',
'Jerusalem artichoke',
'Kale',
'Kiwi',
'Leek',
'Lemon',
'Mango',
'Mangosteen',
'Melon',
'Mushroom',
'Nectarine',
'Okra',
'Olive',
'Onion',
'Orange',
'Parship',
'Pea',
'Pear',
'Pineapple',
'Potato',
'Pumpkin',
'Quince',
'Radish',
'Rhubarb',
'Shallot',
'Spinach',
'Squash',
'Strawberry',
'Sweet potato',
'Tomato',
'Turnip',
'Ugli fruit',
'Victoria plum',
'Watercress',
'Watermelon',
'Yam',
'Zucchini'
];
function searchVeggies (searchString) {
var results = [];
for (var i = 0; i < FRUITS_AND_VEGGIES.length; i++) {
var veggie = FRUITS_AND_VEGGIES[i].toLowerCase();
if (veggie.indexOf(searchString.toLowerCase()) === 0) {
results.push(FRUITS_AND_VEGGIES[i]);
}
}
return results;
}
/**
* #function onload
* #desc Initialize the combobox examples once the page has loaded
*/
window.addEventListener('load', function () {
var ex1Combobox = new aria.ListboxCombobox(
document.getElementById('ex1-combobox'),
document.getElementById('ex1-input'),
document.getElementById('ex1-listbox'),
searchVeggies,
false
);
var ex2Combobox = new aria.ListboxCombobox(
document.getElementById('ex2-combobox'),
document.getElementById('ex2-input'),
document.getElementById('ex2-listbox'),
searchVeggies,
true
);
var ex3Combobox = new aria.ListboxCombobox(
document.getElementById('ex3-combobox'),
document.getElementById('ex3-input'),
document.getElementById('ex3-listbox'),
searchVeggies,
true,
function () {
// on show
document.getElementById('ex3-combobox-arrow')
.setAttribute('aria-label', 'Hide vegetable options');
},
function () {
// on hide
document.getElementById('ex3-combobox-arrow')
.setAttribute('aria-label', 'Show vegetable options');
}
);
document.getElementById('ex3-combobox-arrow').addEventListener(
'click',
function () {
if (ex3Combobox.shown) {
document.getElementById('ex3-input').focus();
ex3Combobox.hideListbox();
}
else {
document.getElementById('ex3-input').focus();
ex3Combobox.updateResults(true);
}
}
);
});
</script>
</body>
</html>
Any help would be appreciated.
I realize that this is an old post, but I have determined why the undefined error is occurring:
There are two js files associated with this example:
ListBox-Combox.js &
ListBox-Combo-example.js
The 'ListBox-Combo-example.js' file has event listeners for all three examples on the page
https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html.
Since you only copied the code for the first example, when the javascript attempts to find the combobox 'ex2-Combobox' it cannot find it so javascript throws an error.
You can comment out these lines in the 'ListBox-Combo-example.js' file:
);
var ex2Combobox = new aria.ListboxCombobox(
document.getElementById('ex2-combobox'),
document.getElementById('ex2-input'),
document.getElementById('ex2-listbox'),
searchVeggies,
true
);
var ex3Combobox = new aria.ListboxCombobox(
document.getElementById('ex3-combobox'),
document.getElementById('ex3-input'),
document.getElementById('ex3-listbox'),
searchVeggies,
true,
and replace with a comma. That should solve the problem.

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)
},

Connecting admin-on-rest with Rails Api

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": {...}
}

Resources