I'm working on a web part using the SharePoint Framework, and my code is basically calling two methods to get information from SharePoint Online, when I call these methods on my render() method, I'm getting something like the following:
--- RENDER START ---
--- RENDER DONE ---
GetItems() => ID: 1 Title: New Office
GetItems() => ID: 2 Title: Mid-Term Reviews
GetItems() => ID: 3 Title: DevOps Training
GetItems() => ID: 4 Title: Work with us!
- GetAttachments() => ID: 2 FileName: reviews.jpeg
- GetAttachments() => ID: 1 FileName: new-office.jpg
- GetAttachments() => ID: 3 FileName: devops.jpeg
- GetAttachments() => ID: 4 FileName: careers.png
I was expecting to call these methods and have their results returned to me, and from there use them on my custom HTML rendering, the expected result would be something like this:
--- RENDER START ---
GetItems() => ID: 1 Title: New Office
- GetAttachments() => ID: 1 FileName: reviews.jpeg
GetItems() => ID: 2 Title: Mid-Term Reviews
- GetAttachments() => ID: 2 FileName: new-office.jpg
GetItems() => ID: 3 Title: DevOps Training
- GetAttachments() => ID: 3 FileName: devops.jpeg
GetItems() => ID: 4 Title: Work with us!
- GetAttachments() => ID: 4 FileName: careers.png
--- RENDER DONE ---
// uses the returned results in custom HTML for the rendering
Right now the code is running "wild", the htmlAttachments variable is receiving no results, and the rendering is completely blank, what should I do to get the results before the rendering?
Here it is my code:
public async GetItems() : Promise<IListItems>
{
let formattedResponse: IListItems = {value: []};
await pnp.sp.web.lists.getByTitle('LIST').items.select('Id, Title, Body, Link').get().then( response => {
response.map( (object: any, i: number) => {
formattedResponse.value.push( {Id : object.Id, Title: object.Title, Body: object.Body, Link : object.Link.Url} );
});
});
return formattedResponse;
}
public async GetAttachments(itemId: number) : Promise<IFileItems>
{
let formattedResponse: IFileItems = {value: []};
await pnp.sp.web.lists.getByTitle('LIST').items.getById(itemId).attachmentFiles.get().then( response => {
response.map( (object: any, i: number) => {
formattedResponse.value.push( {Id : itemId, FileName: object.ServerRelativeUrl} );
});
});
return formattedResponse;
}
public render() : void
{
let htmlAttachments: string = '';
htmlAttachments += '<ul>';
this.GetItems().then( (responseItems: IListItems) => {
responseItems.value.forEach( elementItems => {
console.log("GetItems() ==> ", elementItems.Id, elementItems.Title);
this.GetAttachments(elementItems.Id).then( (responseAtt: IFileItems) => {
responseAtt.value.forEach( elementAtt => {
console.log("\t GetAttachments() ==> ", elementAtt.Id, elementAtt.FileName);
htmlAttachments += '<li>' + 'ID: ' + elementAtt.Id + ' - Title: ' + elementAtt.FileName + '</li>';
});
});
});
});
htmlAttachments += '</ul>';
this.domElement.innerHTML = htmlAttachments;
}
A little late to this, but you can get rid of the GetAttachments method itself.
In the first call, GetItems itself you can get the attachment files and then use them in your HTML.
You can modify from below sample code:
public async GetItems() : Promise<IListItems>
{
let formattedResponse: IListItems = {value: []};
await pnp.sp.web.lists.getByTitle("LIST")
.items
.select("Id", "Title", "Body", "Link","AttachmentFiles")
.expand("AttachmentFiles")
.get()
.then(response => {
response.map((object: any, i: number) => {
formattedResponse.value.push({
Id : object.Id,
Title: object.Title,
Body: object.Body,
Link : object.Link.Url,
FileName: object.AttachmentFiles[0].FileName,
FileUrl : object.AttachmentFiles[0].ServerRelativeUrl
});
});
});
return formattedResponse;
}
Reference - Working with List Items in PnP JS
Related
I have method that renders EJS templates and pass in the i18next.t function for the EJS template to do translations by setting the i18next t function as an attribute on the data object:
const data = {
email: user.email,
id: user.id,
t: i18nT
};
The data object is passed into ejs.renderFile(). The only way I can get the translation in the EJS template to work is when I set the i18nT variable to the t function from the i18next.init() functions call back. Otherwise it comes out blank. I see from the console output that the t function of the i18next instance, i18nInstance, is different to the t function set by the callback when initializing i18next.
function t() {
var _this$translator;
return this.translator && (_this$translator =
this.translator).translate.apply(_this$translator, arguments);
}
versus:
function () {
return _this4.t.apply(_this4, arguments);
}
Why is the t function of the i18nInstance object obtained from calling i18next.createInstance() different than the one from the callback? The one from the instance object doesn not work in the EJS template render.
The full code sample:
let i18nInstance: i18n;
let i18nT;
const i18nextInitOptions = {
backend: {
loadPath: path.join(__dirname, '/locales/{{lng}}/{{ns}}.json'),
addPath: path.join(__dirname, '/locales/{{lng}}/{{ns}}.missing.json')
},
debug: true,
fallbackLng: 'da',
preload: ['da', 'en', 'nl'],
returnEmptyString: false,
returnNull: false,
saveMissing: true
};
i18nInstance = await i18next
.createInstance();
await i18nInstance
.use(i18nextBackend)
.init(i18nextInitOptions, async function (error, t) {
if (error) {
console.log(error)
}
i18nT = t;
});
console.log("i18nT: " + i18nT)
/*
The console.log outputs below show that i18nT when set from the callback is different to the
i18nInstance.t.
i18nT: function () {
return _this4.t.apply(_this4, arguments);
}
*/
console.log("i18nInstance.t: " + i18nInstance.t)
/*
console output:
i18nInstance.t: function t() {
var _this$translator;
return this.translator && (_this$translator =
this.translator).translate.apply(_this$translator, arguments);
}
*/
const data1 = {
email: user.email,
id: user.id,
t: i18nT
};
// Calling htmlFromTemplate with data1 with t = i18nT the translation in the EJS template works.
html = await this.htmlFromTemplate('ejsTemplateName.ejs', data1);
const data2 = {
email: user.email,
id: user.id,
t: i18nextInstance.t
};
// Calling htmlFromTemplate with data2 with t = i18nextInstance.t the translation in the EJS is empty.
html = await this.htmlFromTemplate('ejsTemplateName.ejs', data2);
private htmlFromTemplate(templateName: string, data: Object): Promise<String> {
if (!templateName) return;
const htmlPath = path.join(__dirname, '../assets/mail-templates/' + templateName);
return new Promise((resolve, reject) => {
ejs.renderFile(htmlPath, data,(renderErr, str) => {
if (renderErr) {
appLogger.error('MAIL_RENDER: ' + renderErr, { templateName, data });
reject(renderErr);
} else resolve(str);
});
});
}
As soon as you pass the t function like this:
const data2 = {
email: user.email,
id: user.id,
t: i18nextInstance.t
};
the t function is not bound to its original "this" anymore...
pass it this way:
const data2 = {
email: user.email,
id: user.id,
t: i18nextInstance.t.bind(i18nextInstance)
};
more information here: https://github.com/i18next/i18next/issues/1528#issuecomment-748263313
I have an Effect that is called each time it recives an action of more than one "kind"
myEffect.effect.ts
someEffect$ = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.actionOne, fromActions.actionTwo),
exhaustMap(() => {
return this.myService.getSomeDataViaHTTP().pipe(
map((data) =>
fromActions.successAction({ payload: data})
),
catchError((err) =>
ObservableOf(fromActions.failAction({ payload: err }))
)
);
})
)
);
in my test I tried to "simulate the two different actions but I always end up with an error, while if I try with one single action it works perfectly
The Before Each part
describe('MyEffect', () => {
let actions$: Observable<Action>;
let effects: MyEffect;
let userServiceSpy: jasmine.SpyObj<MyService>;
const data = {
// Some data structure
};
beforeEach(() => {
const spy = jasmine.createSpyObj('MyService', [
'getSomeDataViaHTTP',
]);
TestBed.configureTestingModule({
providers: [
MyEffect,
provideMockActions(() => actions$),
{
provide: MyService,
useValue: spy,
},
],
});
effects = TestBed.get(MyEffect);
userServiceSpy = TestBed.get(MyService);
});
This works perfectly
it('should return successActionsuccessAction', () => {
const action = actionOne();
const outcome = successAction({ payload: data });
actions$ = hot('-a', { a: action });
const response = cold('-a|', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const expected = cold('--b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
This doesn't work
it('should return successAction', () => {
const actions = [actionOne(), actionTwo()];
const outcome = successAction({ payload: data });
actions$ = hot('-a-b', { a: actions[0], b: actions[1] });
const response = cold('-a-a', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const expected = cold('--b--b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
There are two problems in this code.
It suggests that getSomeDataViaHTTP returns two values. This is wrong, the response is no different from your first example: '-a|'
It expects the second successAction to appear after 40 ms (--b--b, count the number of dashes). This is not correct, because actionTwo happens after 20 ms (-a-a) and response takes another 10 ms (-a). So the first successAction is after 20ms (10+10), the second is after 30ms (20+10). The marble is: '--b-b'.
Input actions : -a -a
1st http response : -a
2nd http response : -a
Output actions : --b -b
The working code:
it('should return successAction', () => {
const actions = [actionOne(), actionTwo()];
actions$ = hot('-a-b', { a: actions[0], b: actions[1] });
const response = cold('-a|', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const outcome = successAction({ payload: data });
const expected = cold('--b-b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
Marble testing is cool but it involves some black magic you should prepare for. I'd very much recommend you to carefully read this excellent article to have a deeper understanding of the subject.
I have worked in a web part that fetch items from a SP list and then filter or groups these results.
For each filter action I send a new request to SP with Rest and I am using the rest code to get back the filtered items and show them in the web part.
Right now I have two filter actions triggered by onClick events.
Each action looks like this:
To filter by closed agreements:
private getEnded(): void {
this.props.provider.getEnded().then((originalitems: IList[]) => {
this.setState({
filteredListItems: originalitems,
filter: true
});
});
}
and to filter by last day passed:
private getPassed(): void {
this.props.provider.getPassed().then((originalitems: IList[]) => {
this.setState({
filteredListItems: originalitems,
filter: true
});
});
}
and in the dataprovider file I have these methods, each one of them make request to SharePoint, the only difference is the filter parameter:
To get closed agreements (called from the method getEnded()):
public async getEnded(): Promise<IList[]> {
let today = new Date();
let Agreements: IList[] = [];
const items = await sp.web.lists
.getByTitle("AgreementDatabase")
.items.select(this.select)
.filter(
`(AgreementEnded eq true) and (AgreementEndDate le '${today.toISOString()}')`
)
.expand("SalesManager,TaxCatchAll,FrameworkAgreement")
.get();
items.forEach(item => {
Agreements.push({
Title: item.Title,
Id: item.Id,
CustomerAgreementNr: item.CustomerAgreementNr,
AgreementType: item.AgreementType,
Customer: item.TaxCatchAll[0].Term,
FrameworkAgreement: item.FrameworkAgreement.Title,
ContactPerson: item.ContactPerson,
SalesManager: item.SalesManager.FirstName + " " + item.SalesManager.LastName,
DeliveryType: item.DeliveryType,
AgreementStartDate: item.AgreementStartDate,
AgreementEndDate: item.AgreementEndDate,
AgreementEnded: item.AgreementEnded,
LastPriceAdjustment: item.LastPriceAdjustment,
NextPriceAdjustment: item.NexPriceAdjustment,
});
});
return new Promise<IList[]>(async resolve => {
resolve(Agreements);
});
}
and to get passed passed agreements (called from the method getPassed()):
public async getPassed(): Promise<IList[]> {
let today = new Date();
let Agreements: IList[] = [];
const items = await sp.web.lists
.getByTitle("AgreementDatabase")
.items.select(this.select)
.filter(`LastPriceAdjustment le '${today.toISOString()}'`)
.expand("SalesManager,TaxCatchAll,FrameworkAgreement")
.get();
items.forEach(item => {
Agreements.push({
Title: item.Title,
Id: item.Id,
CustomerAgreementNr: item.CustomerAgreementNr,
AgreementType: item.AgreementType,
Customer: item.TaxCatchAll[0].Term,
FrameworkAgreement: item.FrameworkAgreement.Title,
ContactPerson: item.ContactPerson,
SalesManager: item.SalesManager.FirstName + " " + item.SalesManager.LastName,
DeliveryType: item.DeliveryType,
AgreementStartDate: item.AgreementStartDate,
AgreementEndDate: item.AgreementEndDate,
AgreementEnded: item.AgreementEnded,
LastPriceAdjustment: item.LastPriceAdjustment,
NextPriceAdjustment: item.NexPriceAdjustment,
});
});
return new Promise<IList[]>(async resolve => {
resolve(Agreements);
});
}
As you can see the method that request information from SP are almost identical, the only difference is the filter parameter.
I am wondering how can I resuse just one of this rest method tho fetch and filter data from sharepoint?
Best regards
Americo
You can write one method and pass the filer:
private getEnded(): void {
const filter = `(AgreementEnded eq true) and (AgreementEndDate le '${today.toISOString()}')`;
this.props.provider.getAgreements(filter).then((originalitems: IList[]) => {
this.setState({
filteredListItems: originalitems,
filter: true
});
});
private getPassed(): void {
const filter = `LastPriceAdjustment le '${today.toISOString()}'`;
this.props.provider.getAgreements(filter).then((originalitems: IList[]) => {
this.setState({
filteredListItems: originalitems,
filter: true
});
});
public async getAgreements(filterAgreements: string): Promise<IList[]> {
let today = new Date();
let Agreements: IList[] = [];
const items = await sp.web.lists
.getByTitle("AgreementDatabase")
.items.select(this.select)
.filter(filterAgreements)
.expand("SalesManager,TaxCatchAll,FrameworkAgreement")
.get();
items.forEach(item => {
Agreements.push({
Title: item.Title,
Id: item.Id,
CustomerAgreementNr: item.CustomerAgreementNr,
AgreementType: item.AgreementType,
Customer: item.TaxCatchAll[0].Term,
FrameworkAgreement: item.FrameworkAgreement.Title,
ContactPerson: item.ContactPerson,
SalesManager: item.SalesManager.FirstName + " " + item.SalesManager.LastName,
DeliveryType: item.DeliveryType,
AgreementStartDate: item.AgreementStartDate,
AgreementEndDate: item.AgreementEndDate,
AgreementEnded: item.AgreementEnded,
LastPriceAdjustment: item.LastPriceAdjustment,
NextPriceAdjustment: item.NexPriceAdjustment,
});
});
return new Promise<IList[]>(async resolve => {
resolve(Agreements);
});
Other improvement:
You can use .map() instead of .foreach()
Agreements = items.map(item => {
Title: item.Title,
Id: item.Id,
CustomerAgreementNr: item.CustomerAgreementNr,
AgreementType: item.AgreementType,
Customer: item.TaxCatchAll[0].Term,
FrameworkAgreement: item.FrameworkAgreement.Title,
ContactPerson: item.ContactPerson,
SalesManager: item.SalesManager.FirstName + " " + item.SalesManager.LastName,
DeliveryType: item.DeliveryType,
AgreementStartDate: item.AgreementStartDate,
AgreementEndDate: item.AgreementEndDate,
AgreementEnded: item.AgreementEnded,
LastPriceAdjustment: item.LastPriceAdjustment,
NextPriceAdjustment: item.NexPriceAdjustment,
});
I have this working fine with get.JSON but when I try and use the fetch API instead, it gives me the error "Required parameter: part".
export const fetchYoutube = () => {
return dispatch => {
fetchAsync()
.then(data => console.log(data))
.catch(reason => console.log(reason.message))
dispatch({
type: INCREMENT
})
}
}
async function fetchAsync () {
var query = {
part: 'snippet',
key: 'AIzaSyA3IHL73MF00WFjgxdwzg57nI1CwW4dybQ',
maxResults: 6,
type: 'video',
q: 'music'
}
let response = await fetch('https://www.googleapis.com/youtube/v3/search', {
data : query,
method: 'GET'
});
let data = await response.json();
return data;
}
How do I pass the query object using the fetch API?
Try attaching the query as params:
replace:
let response = await fetch('https://www.googleapis.com/youtube/v3/search', {
data : query,
method: 'GET'
});
with:
var url = new URL("https://www.googleapis.com/youtube/v3/search"),
query = {
part: 'snippet',
key: '#####################################',
maxResults: 6,
type: 'video',
q: 'music'
}
Object.keys(query).forEach(key => url.searchParams.append(key, query[key]))
let response = await fetch(url)
Setting query string using Fetch GET request
hello can any one guide me in my ajax request its always show me only eight record .
$(this).typeahead({
source: function (query, process) {
jQuery.ajax({
url: url + "/ajax/productdetail",
type: 'GET',
limit: 10,
data: {
cat: $type,
name: query,
form: $form,
setupid: $setupid
},
dataType: 'json',
success: function (response) {
objects = [];
map = {};
$.each(response, function (i, object) {
//debugger;
map[object.name] = object;
objects.push(object.name);
});
process(objects);
}
});
}
});
Just add an option "items" while instantiating the typeahead method. Hope the below sample will help you to understand.
$('input#auto-complete-field').typeahead({
minLength : 0,
items: 9999,
source: function (query, process) {
objects = [];
map = {};
$.ajax({
.......
......
});
}
updater: function(item) {
.............
.............
}
});