How to use the pageContext in SPFx? - spfx

I am trying to get a value from the current page by using the pageContext but I am getting either undefined or 404.
This is the situation:
In the Site pages library there are several news pages. Each news page has some tags attached to them. This tags lives in a custom column in the Site Pages library.
There are news that have 1 tag and other several tags. It can be the situation where two or more news share the same tag(s).
The goal is when I open a news page the tags that are attached to that news are also visible.
Until now I am using #pnp/pnpjs and the code looks like this:
var result: any = await sp.web.lists.getByTitle("Site Pages")
.items.getById(15)
.select("Tags")
.get();
return await result.Tags;
And it is giving me 404 error
I also tried this one:
this.context.pageContext.list('Site Pages').listItem['Tags'].get().then((items: any[]) => {
console.log(items);
});
But it giving me Cannot read property 'list' of undefined
Du you have an idea how can get the value of the Tags column asociated with the current news?
Here is an update
Now I am getting the right tag. The question now is how to show it in the screen?
import * as React from 'react';
import styles from './ReadTags.module.scss';
import { IReadTagsProps } from './IReadTagsProps';
import { sp } from '#pnp/pnpjs';
export default class ReadTags extends React.Component<IReadTagsProps, {}> {
constructor(props: IReadTagsProps) {
super(props);
}
private async getTags() {
var id = this.props.context.pageContext.listItem.id;
var result: any = await sp.web.lists.getByTitle("Site Pages")
.items.getById(id)
.select("Tags")
.get();
return await result.Tags;
}
public render(): React.ReactElement<IReadTagsProps> {
console.log(this.getTags());
return (
<div className={ styles.readTags }>
<div className={ styles.container }>
<div className={ styles.row }>
<div className={ styles.column }>
</div>
</div>
</div>
</div>
);
}
}
Regards
Amerco

What you'll probably want to do is store your tags in the state of your component. Then you can show these (if the value from state is not empty) during your render. I can highly recommend working through the React tutorial to understand React lifecycle and state/props.
https://reactjs.org/tutorial/tutorial.html
https://reactjs.org/docs/state-and-lifecycle.html
Something with getting your data in componentDidMount, storing it in the state by using this.setState and then running through them in render with this.state.tags. It's more of a React question then a SPFx question :)
There's a ton of samples here with SPFx and React:
https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples

Related

Remix.run - common shared components

I’m just getting started learning remix.run and whilst I’ve gone through the tutorials there’s one bit I’m stuck on how I should implement it in remix.
If I wanted to display a common header that might toggle a sign in/sign out button based on the users logged in state where would this live?
My nextjs thinking would be to create the components and reference them in the common document. I know I can do this in the remix.server and remix.client files, but as my “login” component is and isn’t a route (I.e I might want to POST to the route when a user submits the login form but GET /login isn’t really a route) how would you structure something like this and would doing this even allow me to have loader and action functions in the shared component?
Do I just need to adjust my thinking about how to achieve this in remix or am I overthinking it and the above is perfectly valid?
I tried the following and it works. But then I end up just creating an empty "logout" route to process the form data with an action and loader that process the form in the case of the action or just redirect if a GET request via the loader. Is this the best approach?
export const SignIn = ({user}) => {
return (
<>
<form method="POST"action="/logout">
<input type="hidden" id="some" value="foo" />
{user ?
(
<button>sign out</button>
)
: (
<button>sign in</button>
)
}
</form>
</>
)
}
Thanks
based on https://remix.run/docs/en/v1/tutorials/jokes#build-the-login-form
it does indeed look like an empty route for logout:
import type { ActionFunction, LoaderFunction } from "#remix-run/node"
import { redirect } from "#remix-run/node"
import { logout } from "~/utils/session.server"
export const action: ActionFunction = async ({request}) => {
return logout(request);
};
export const loader: LoaderFunction = async () => {
return redirect("/");
};

How to show data of a single id using Vue js?

I'm displaying all records on a page at this URI xxx.test/employer/search/filter-by. I'm using Algolia Search to display all of the records/results. I added a button called View Profile and attached an empty method to it called showProfile.
When a user clicks on this button, I want to display this specific profile/record on a new page by itself. If I was fetching data on my own, i.e. without Algolia's code I would be able to do this, but in this case I'm not really sure how to do this.
EmployerSearchComponent.vue:
<ais-instant-search
:search-client="searchClient"
index-name="candidate_profiles"
>
<ais-search-box v-model="query" class="mb-3" placeholder="Search by job title, company name or city..." />
<ais-configure
:restrictSearchableAttributes="['job_title', 'employment_type']"
:hitsPerPage="25"
/>
<b-row class="job-search-card">
<ais-hits class="w-100">
<template slot="item" slot-scope="{ item }">
<b-col cols="12" class="mb-3">
<b-card>
<div class="float-right">
<i class="flaticon-paper-plane"></i> View Profile
</div>
<h4 style="margin-bottom: 20px;"><router-link to="/">{{item.job_title}}</router-link></h4>
<p>Type: {{item.employment_type}}</p>
<b-card-text class="mt-2"><span v-if="item.experience && item.experience.length < 300">{{item.experience}}</span><span v-else>{{item.experience && item.experience.substring(0,300)+"..."}}</span></b-card-text>
</b-card>
</b-col>
</template>
</ais-hits>
<ais-pagination />
</b-row>
</ais-instant-search>
If I click on the network tab in the console, and on the algolia query search URI, I can see that the search results are in results[0].hits. Below is a screenshot of this data.
P.S. My data is empty it just contains algolia client ID's.
How can I display a single id on a new page? I understand that I need to get the id from the record that is being displayed, and show this information on a new page, but I don't know how to put it all together.
Again I think I'll need a route, so I created this
Route::get('/employer/search/filter-by/show/{id}', 'EmployerSearchController#show')->name('employer.search.show');
I'm using Laravel for my backend. Thanks in advance.
------------------------------------- UPDATED: -------------------------------------
I feel like I'm really close, but $itemId in my controller after I die and dump returns "undefined" for some reason.
router.js (Vue Router):
{
path: '/employer/search/filter-by/:itemId/show',
name: 'employer-search-index',
component: SearchIndex,
meta: {
breadcrumb: 'Search Candidates',
requiresAuthEmployer: true,
employerHasPaid: true
},
},
EmployerSearchComponent.vue - with the <router-link> button:
<template slot="item" slot-scope="{ item }">
<b-col cols="12" class="mb-3">
<b-card>
<div class="float-right">
<router-link class="apply-job-btn btn btn-radius theme-btn apply-it" :to="'/employer/search/filter-by/' + item.id + '/show'">View Profile</router-link>
</div>
<h4 style="margin-bottom: 20px;"><router-link to="/">{{item.job_title}}</router-link></h4>
<p>Type: {{item.employment_type}}</p>
<b-card-text class="mt-2"><span v-if="item.experience && item.experience.length < 300">{{item.experience}}</span><span v-else>{{item.experience && item.experience.substring(0,300)+"..."}}</span></b-card-text>
</b-card>
</b-col>
</template>
EmployerSearchController.php:
public function show ($itemId)
{
$candidateProfiles = CandidateProfile::with(['user', 'photo', 'resume', 'video'])
->where('id', '=', $itemId)->get();
return Response::json(array(
'candidateProfiles' => $candidateProfiles,
), 200);
}
api.php:
Route::get('/employer/search/filter-by/{itemId}/show', 'EmployerSearchController#show')->name('employer.search.show');
And Finally, in the .vue file that shows the Full Single Profile, I'm loading the data like this.
mounted() {
this.loadCandidateProfileData();
},
methods: {
loadCandidateProfileData: async function () {
try {
const response = await employerService.loadCandidateProfileData();
this.candidateProfiles = response.data.candidateProfiles;
} catch (error) {
this.$toast.error("Some error occurred, please refresh!");
}
},
}
And the employerService.js file from the above code:
export function loadCandidateProfileData(itemId, data) {
return http().get(`employer/search/filter-by/${itemId}/show`, data);
}
As you suggest, you'll need an API endpoint to fetch the data from, returning it as a JSON object. You'll need to add a route to your client-side routes that takes the job ID (or slug) as a parameter. In your job component, you can retrieve the route param (e.g. in the created() method) as $route.params.id, for example, and use that to fetch the data from your API.
If your Algolia search is returning all the data that you want to display on your single job listing page, you could just put that in your Vuex store and display it without having to make another HTTP request. The downside of that would be that if a user bookmarked the page to return to later, there'd be no data in the store to display.
Thank you to Nilson Jacques responses. If you follow our conversation.
Finally I got the itemId param from the route and passed it to loadCandidateProfileData() like this:
loadCandidateProfileData: async function() {
try {
const itemId = this.$route.params.itemId;
const response = await employerService.loadCandidateProfileData(itemId);
this.candidateProfiles = response.data.candidateProfiles;
console.log(this.candidateProfiles);
if (response.data.candidateProfiles.current_page < response.data.candidateProfiles.last_page) {
this.moreExists = true;
this.nextPage = response.data.candidateProfiles.current_page + 1;
} else {
this.moreExists = false;
}
} catch (error) {
this.$toast.error("Some error occurred, please refresh!");
}
},

Avoid double ajax call

I'm really new to React so I'm trying to manage it by made some examples. I made this component that made an ajax call to render a simple list.
import React from "react";
import axios from 'axios';
import Page from '../components/Page/Page';
import ListJobs from '../components/ListJobs/ListJobs';
let state ;
class Home extends React.Component{
constructor(props){
super(props);
this.state ={jobs:[]};
}
componentDidMount(){
var _this = this;
this.serverRequest = axios.get("http://codepen.io/jobs.json")
.then(function(result){
_this.setState({
jobs:result.data.jobs
});
});
}
componentWillUnmount() {
}
render(){
return(
<div>
<Page title="Home">
<p>Home</p>
<ul>
{this.state.jobs.map(function(job,index) {
return(
<ListJobs key={index} job={job}/>
);
})}
</ul>
</Page>
</div>
);
}
}
export default Home;
It calls another child component to render the li elements.
Everytime I call this component it starts with this ajax call, so I was wondering if there is a way to save the result of this ajax call and re-use it, instead of launching every time the request. I tried to do like this https://jsfiddle.net/corvallo/mkp4w8vp/
But I receive this error in the developer console:
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of Home
Thank you in advance for your help
If you want the ajax calls only when the app launches, then you can make ajax calls on the parent component (probably App) and then pass it as a props to the Home component
EDIT
if you want to call the ajax only from the component, I think you can implement a cache or some sort e.g using localstorage
example
componentDidMount(){
let cache = JSON.parse(localStorage.getItem('homeCache') || "{}");
if(cache.hasOwnProperty('cached') && cache.hasOwnProperty('jobs')){
this.setState({jobs:cache.jobs});
}else{
(/*YOUR API HERE*/).then(function(result){
_this.setState({jobs:result.data.jobs});
localStorage.setItem('homeCache', JSON.stringify({cached: true, jobs: result.data.jobs}));
});
}
}
and everytime when the user exits the app, clear the cache (or anytime you want the cache to be cleared)
localStorage.setItem('homeCache', JSON.stringify({}));
I think that's one solution which popped out of my head right now..

Aurelia - Passthrough Routes

Is it possible to render static HTML fragments without having an associated view-model in Aurelia? For instance, I have a typical header, body, footer layout. In body, I have the router view. There are a set of links in the footer such as FAQs upon clicking which I want to render a view in the body area.
When I try to define a route config for the faq route, the config is expecting one of You must specify a "moduleId:", "redirect:", "navigationStrategy:", or "viewPorts:".
The temporary work around that I have is to create a passthrough view model that doesn't do anything. This is resulting in a bunch of passthrough view model classes. I am sure I am doing something wrong.
I couldn't find any help online with this use-case. Any references will be highly appreciated.
It seems like you're looking for an inlineView("<your html here/>") type of functionality for routes so that navigating to the target route will directly render the HTML in the router-view element.
This is not directly possible with aurelia-router because without a ViewModel, no ActivationStrategy can be invoked. Aurelia-router wants to call canActivate, activate, canDeactivate, deactivate on something.
However, if you simply want to define markup programmatically, and you don't want to declare a ViewModel for each individual piece of markup, then that can be solved quite neatly with the compose element in combination with inlineViewStrategy.
With this approach, you only need one View/ViewModel pair which is responsible for retrieving the correct HTML based on the current route, and then render that HTML.
There are also other ways to do this, but AFAIK this approach involves the least amount of plumbing.
Of course you also need an object to store the HTML/route pairs in, and a service to store/retrieve those objects.
You can see a live working version here (including a few comments to clarify things):
https://gist.run/?id=8c7e02ce1ee0e25d966fea33b826fe10
app.js
import { inject } from "aurelia-framework";
import { Router } from "aurelia-router";
import { FaqService } from "./faq-service";
#inject(Router, FaqService)
export class App {
constructor(router, faqService) {
router.configure(config => {
config.map({ route: "", moduleId: "./empty" });
config.map({ route: "faq/:route", moduleId: "./faq-detail" });
});
this.router = router;
this.faqService = faqService;
}
openFaq(item) {
this.router.navigate(`faq/${item.route}`);
}
}
app.html
<template>
<router-view></router-view>
<ul>
<li repeat.for="item of faqService.faqItems" click.delegate="openFaq(item)">
${item.title}
</li>
</ul>
</template>
empty.js (just a convenience for default empty route):
import { inlineView } from "aurelia-framework";
#inlineView("<template>no content</template>")
export class Empty {}
faq-service.js
import { singleton } from "aurelia-framework";
class FaqItem {
constructor(route, title, markup) {
this.route = route;
this.title = title;
this.markup = markup;
}
}
#singleton(false)
export class FaqService {
constructor() {
this.faqItems = [
new FaqItem("question-1", "Question 1", "<h4>Question 1</h4><p>Answer 1</p>"),
new FaqItem("question-2", "Question 2", "<h4>Question 2</h4><p>Answer 2</p>"),
new FaqItem("question-3", "Question 3", "<h4>Question 3</h4><p>Answer 3</p>")
];
}
getByRoute(route) {
return this.faqItems.find(i => i.route === route);
}
}
faq-detail.js
import { inject, InlineViewStrategy } from "aurelia-framework";
import { FaqService } from "./faq-service";
#inject(FaqService)
export class FaqDetail {
constructor(service) {
this.service = service;
}
activate(param) {
let item = this.service.getByRoute(param.route);
this.viewStrategy = new InlineViewStrategy(`<template>${item.markup}</template>`)
}
}
faq-detail.html
<template>
<compose view.bind="viewStrategy"></compose>
</template>

use parse react query results as an html tag attribute

This is my first time asking a question so I am a true SO newbie. I am currently working on a mobile app and I am using Parse React and Ratchet to build it. I have read the React documentations on FB github and apparently do not understand all enough to solve some problems. One of my problems is using the results of a Parse Query in the observe function of the declared ParseComponent as a value of a rendered react component, which in turn attempts to render the passed value as HTML. Below is the parent object:
export default class CategoryPage extends ParseComponent
{
observe(props,state){
return{
category: new Parse.Query('BusinessCategory').equalTo("objectId", this.props.categoryId)
};
}
render() {
return (
<div>
<Header text={this.data.category.objectId} back="true"/>
<div className="content">
<BusinessList categoryId={this.data.category.objectId}/>
</div>
<NavBar />
</div>
);
}
};
Notice I am passing the objectId of the category found in the Query as a text attribute of the Header React component. I am expecting Header as a child to use the passed property as follows:
var Header = React.createClass({
render: function () {
return(
<header className="bar bar-nav">
<h1 className="title">{this.props.text}</h1>
</header>
);
}
});
However the h1 is not rendering anything! I am thinking that this.data.category.objectId is a string and therefore should be rendered in the h1 tag as a string.
I do appreciate your answers very much.

Resources