Load adds from JobAdder on Nuxt page - vue.js

I have a Nuxt website/blog where I'd like to render some job ads on one particular page so not changing the route structure and creating children pages for each job posted.
This pen gives an idea of what I'm trying to do:
<div id="ja-jobs-widget"></div>
<script>
var _jaJobsSettings = {
key: "bbhc36kiqfoudkoc4yd7qaihf4",
jobListSettings: {
titleIsLink: false
},
jobDetailsSettings: {
showBulletPoints: false
},
applicationFormSettings: {
useExternalApplicationForm: true
}
};
</script>
<script src="//jobadder.com/widgets/v1/jobs.min.js"></script>

Related

How Do I Display Product Images In Shopware 6 Administration

I am working on a Shopware 6 Administrative plugin but displaying product images has been a big headache. I went through shopware repositories of 'product_media', 'media', and generally all folder repositories related to media.
I have not been able to decipher how image linking works since I can not get hold of the exact folder names.
How then do I go about this. Note: I have been able to get correct image names and relevant image data like id, folder id etc.
Below is my module/component idex.js file codes
import template from './images-page.html.twig';
const { Component, Context } = Shopware;
const { Criteria } = Shopware.Data;
Component.register('images', {
template,
inject: ['repositoryFactory', 'mediaService', 'acl'],
metaInfo() {
return {
title: 'images'
};
},
computed: {
/**productMediaRepository() {
return this.repositoryFactory.create(this.product.media.entity);
},*/
productRepository() {
return this.repositoryFactory.create('product');
},
mediaFolderRepository() {
return this.repositoryFactory.create('media_folder');
},
mediaRepository() {
return this.repositoryFactory.create('media');
},
rootFolder() {
const root = this.mediaFolderRepository.create(Context.api);
root.name = this.$tc('sw-media.index.rootFolderName');
root.id = null;
return root;
},
logRep(){
console.log(this.productRepository);
// console.log(this.mediaFolderRepository);
// console.log(this.mediaRepository);
// console.log(this.rootFolder);
}
},
methods: {
logProducts(){
const criteria = new Criteria();
this.productRepository
.search(criteria, Shopware.Context.api)
.then(result => {
console.log(result[0]);
});
},
logMediaFolders(){
const criteria = new Criteria();
this.mediaFolderRepository
.search(criteria, Shopware.Context.api)
.then(result => {
console.log(result);
});
}
},
created(){
this.logMediaFolders();
}
});
here's the twig template (nothing really here)
<sw-card title="Watermark">
<img src="" alt="Product Image" />
</sw-card>
The media elements of the products are associations that are not loaded automatically, but you can configure the criteria, so that those associations will be loaded directly when you fetch the products. Refer to the official docs for detailed infos.
In you case that means to load the cover image / or all product images, you would have to adjust the criteria you use for fetching the products the following way
logProducts(){
const criteria = new Criteria();
criteria.addAssociation('cover.media');
criteria.addAssociation('media.media');
this.productRepository
.search(criteria, Shopware.Context.api)
.then(result => {
console.log(result[0]);
});
},
Then to link to the cover image you can use:
<sw-card title="Watermark">
<img src="product.cover.media.url" alt="Product Image" />
</sw-card>
You find all the media elements of the product as an array under product.media and can also use product.media[0].media.url to link to those images.

Mithril: how to use multiple root elements?

When using m.route, does everything under it have to be rendered using Hyperscript (not counting JSX)? Is it possible to mix pure HTML with multiple separate parts rendered using Hyperscript?
Suppose I have this HTML code:
<div id="root">
<div class="header">
<h3 id="header-text">Header Text</h3>
</div>
<div class="container">
<div id="content"></div>
<div id="sidebar"></div>
</div>
<div id="footer"></div>
</div>
Is it possible to use Mithril to render multiple parts of the HTML sections? Like so (pseudo code):
var root = document.getElementById('root');
var header = document.getElementById('header');
var content = document.getElementById('content');
var sidebar = document.getElementById('sidebar');
var footer = document.getElementById('footer');
var HeaderComponent = ...; // to render header text
var ContentComponent = ...; // main page content
var SidebarComponent = ...; // show links, bio, etc.
var Footer = ...; // contact info, etc.
var index = {
view: function(){
return [
{el: header, component: HeaderComponent},
{el: content, component: ContentComponent},
{el: sidebar, component: SidebarComponent},
{el: footer, component: FooterComponent}
];
}
};
m.route(root, '/', {
'/': index
});
so that multiple separate parts in the HTML code are rendered instead of just having a single root element? I really don't want to render the whole HTML skeleton template in Mithril as well, like this:
❌ DO NOT WANT:
// I DON'T want this
m('div#root', [
m('div.header', [
m('h3#header-text', m(HeaderComponent))
]),
m('div.container', [
m('div#content', m(ContentComponent)),
m('div#sidebar', m(SidebarComponent))
]),
m('div#footer', m(FooterComponent))
]);
I know it's manageable, but I'd really like the base skeleton template to be inside the .HTML file, so that I can seamlessly wrap more HTML tags later on, like using Bootstrap classes (cards, container, rows, extra wrapping divs needed, etc.). Thanks :)!
There's nothing stopping you from using the m.mount function several times, and several mount-points can be initialised this way:
var count = 0
m.mount(root1, {
view: () =>
m('button', {
onclick: () => count++,
textContent: 'Increase count',
}),
})
m.mount(root2, {
view: () =>
m('p', 'Count: ', count)
})
<h1>
Static page with multiple roots
</h1>
<div id=root1>
</div>
<p>
Intermediary static content
</p>
<div id=root2>
</div>
<script src="https://unpkg.com/mithril#2.0.4/mithril.min.js"></script>
Using #Barney's answer below, I managed to solve this using a mix of m.route for the main content and multiple m.mount's for the other components:
var header = document.getElementById('header');
var content = document.getElementById('content');
var sidebar = document.getElementById('sidebar');
var footer = document.getElementById('footer');
var HeaderComponent = ...; // to render header text
var SidebarComponent = ...; // show links, bio, etc.
var Footer = ...; // contact info, etc.
// this will be shown under content
var HomePage = {
view: function(){
return m('p', [
'Lorem ipusom dolor amit ',
m(m.route.Link, {href: '/about'}, 'About')
]);
}
};
var AboutPage = {
view: function(){
return m('p', 'This is the About page!');
}
};
//// MAIN SOLUTION ////
var Layout = {
// oninit is only run once
oninit: function(){
m.mount(header, HeaderComponent);
m.mount(sidebar, SidebarComponent);
m.mount(footer, FooterComponent);
},
// run on every route change:
view: function(vnode){
return m('div', vnode.children);
}
};
m.route(content, '/', {
// using RouteResolver
'/': {
render: function(){
return m(Layout, m(HomePage));
},
onmatch: function(){
// this will be called on route change, so update your mounted components as needed
HeaderComponent.title = "Home Page";
}
},
'/about': {
render: function(){
return m(Layout, m(AboutPage));
},
onmatch: function(){
HeaderComponent.title = "About Page";
}
}
});
m.route is only used for <div id="content">, while all the other separate components (header, sidebar, footer) are instantiated using m.mount. I've wrapped the router with a Layout component that's used for all the routes, as this allows me to init and mount the separate components that will remain when routes change and not be affected by the main router. The router had to be modified to use RouteResolver to allow for more control and flexbility.
Notes:
Only a single m.route allowed per application, but multiple m.mount's allowed
When the same component is assigned multiple routes (in this case, Layout), the subtree will NOT be deleted and rebuilt from ground up -- it will only be diffed and updated (source). This way, we can use Layout component's oninit lifecycle hook to mount our other separate components, because oninit will only be called once.
We must use a RouteResolver for the routes in m.route, which gives us more control over how the routes should be rendered and we also get hooks like onmatch, which fires BEFORE the route is initialized -- this is the perfect place to update our mounted components as needed!

Vue rendering array of objects

I'm creating a basic app in vue that uses axios to make a get request to grab html data from a blog site and using the cheerio node package to scrape the site for elements such as blog title and the date posted of each blog articles. However, I'm having trouble trying to render the scraped elements into the html. Here's the code:
<template>
<div class="card">
<div
v-for="result in results"
:key="result.id"
class="card-body">
<h5 class="card-title">{{ result.title }}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ result.datePosted }}</h6>
</div>
</div>
</template>
<script>
const Vue = require('vue')
const axios = require('axios')
const cheerio = require('cheerio')
const URL = 'https://someblogsite.com'
export default {
data() {
return {
results: []
}
},
mounted: function() {
this.loadBlogs()
},
methods: {
loadBlogs: function() {
axios
.get(URL)
.then(({ data }) => {
const $ = cheerio.load(data)
let results = this
$('.post').each((i, element) => {
const title = $(element)
.children('.content-inner')
.children('.post-header')
.children('.post-title')
.children('a')
.text()
const datePosted = $(element)
.children('.content-inner')
.children('.post-header')
.children('.post-meta')
.children('.posted-on')
.children('a')
.children('.published')
.text()
this.results[i] = {
id: i + 1,
title: title,
datePosted: datePosted
}
})
})
.catch(console.error)
}
}
}
</script>
I tried declaring
let results = this
before the axios request to refer to the scope within export default, but still getting the indicator from VS Code that the scope is still within the loadBlogs function. Am I missing something? I greatly appreciate the help! Thanks!
I think your problem is that you're trying to set Property of an results array so Vue can't pick your data update. Instead you should construct new array from your parsed page and set it as this.results = newResultsArray:
loadBlogs: function() {
axios.get(URL).then(({data}) => {
const $ = cheerio.load(data)
const newResults = $('.post').map((i, element) => {
const title = $(element).children('.content-inner .post-header .post-title a').text()
const datePosted = $(element).children('.content-inner .post-header .post-meta .posted-on a .published').text()
return {
id: i + 1,
title: title,
datePosted: datePosted
}
})//.toArray() // this toArray call might be needed, I haven't worked with cheerio for some time and not sure whether it returns array or its own collection type like jQuery does
this.results = newResults;
}).catch(console.error)
}
Also it should be even simpler if you just use this.results.push({...}) instead of property assignment this.results[i] = {...} (but it is usually easier to handle whole arrays instead of inserting and removing parts of them, both are viable solutions in their respective use cases, though).
And please check out this documentation article about how Vue handles reactive updates, it describes the problem you've encountered.

Vue-Select: Pushing a 2 dimensional array to :options

The plugin Vue-Select.
What I was trying to do is, make a search-select-dropdown input based on database.
So here's my SQL first named Ms_Location.
id_Loc | name_Loc
LOC0001 | Indonesia
LOC0002 | China
LOC0003 | America
My index.php
<!DOCTYPE html>
<html>
<head>
</head
<body>
<div class="form-group">
<label for="lokasi_id" class="control-label required"><strong>Lokasi</strong></label>
<v-select :options="lokasi_list" placeholder='Type location..'></v-select>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script src="https://unpkg.com/vue-select#latest"></script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue ({
el: '#app',
data: {
lokasi_select: '',
lokasi_list: [],
},
// End of data
computed: {
get_lokasi() {
var list_loc = new Array();
list_loc = <?php include('receive_lokasi.php') ?>;
for(var i=0; i<list_loc.length; i++) {
var pushLoc = {
label: list_loc[i][1], value: list_loc[i][0]
}
this.lokasi_list.push(pushLoc);
}
return list_loc[0][1];
}
}
})
});
</script>
</body>
</html>
And this is my receive_lokasi.php
<?php
include ('koneksi.php');
$condition = "1";
if(isset($_GET['userid'])){
$condition = " id=".$_GET['userid'];
}
$sqltran = mysqli_query($con, "SELECT id_Loc, name_Loc FROM ms_location")or die(mysqli_error($con));
$response = array();
while ($rowList = mysqli_fetch_array($sqltran,MYSQLI_NUM)) {
$response[] = $rowList;
}
echo json_encode($response);
mysqli_close($con);
?>
However, I can't seem to get the option shown. This only happens after I make the get_lokasi(). So the mistake is probably there? Or perhaps I was missing something.
I've tried to print the lokasi_list somewhere, and yes, the value is there, but not shown in the dropdown bar.
Also, I'm new to Vue, so any help would be good. Thanks!
Nevermind..
My mistake, I didn't notice my receive_lokasi.php code
Instead of using MYSQLI_NUM
while ($rowList = mysqli_fetch_array($sqltran,MYSQLI_NUM)) {
$response[] = $rowList;
}
I should be using MYSQLI_ASSOC, as documented in here.
while ($rowList = mysqli_fetch_array($sqltran,**MYSQLI_ASSOC**)) {
$response[] = $rowList;
}
After that change this
<v-select :options="lokasi_list" placeholder='Type location..'></v-select>
To this
<v-select label='nama_Location' :options="lokasi_list" placeholder='Type location..'></v-select>
After that, everything loads fine.
Vue's computed properties aren't normally used to populate vue data attributes, they normally take one or more data attributes and combine them into something different for the template to use.
In your code you've tried to populate the vue data attribute 'lokasi_list' in the computed property 'get_lokasi', but you never call 'get_lokasi' anywhere in the template so lokasi_list remains empty.
Another approach to this sort of situation is to use a vue method to fetch data from the php backend via an ajax call with something like axios, and you'd normally use that method in the vue app's created life cycle event to get the data asap.
e.g.
<script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue({
el: '#app',
data: {
lokasi_select: '',
lokasi_list: [],
},
created: function() {
this.fetchLocations();
},
methods: {
fetchLocations: function() {
axios.get('/api/locations-end-point')
.then((response) => {
this.lokasi_list = response.data //might need to change this to match how your php is returning the json
})
.catch((error) => {
//handle the error
})
}
}
});
</script>
Sorry to mention this, but in your php you've got:
if(isset($_GET['userid'])){
$condition = " id=".$_GET['userid'];
}
That looks like you were planning to use it as part of your sql, but it would have been vulnerable to SQL injection attacks, sorry If I'm pointing out something you already knew.

Why are the views in my polymer app not getting loaded?

I am working on creating a Polymer app for a pet project, using the Polymer Starter Kit, and modifying it to add horizontal toolbar, background images, etc. So far, everything has worked fine except the links in the app-toolbar do not update the "view" when I click on them.
All my debugging so far points me in the direction of the "page" property. I believe this is not getting updated or is null, causing the view to default to "about" (which is View-2 as per the starter kit) as specified in the _routePageChanged observer method.
I tried using the debugger on DevTools on Chrome, but being new to this, I'm not very clear if I did it correctly. I just kept going in and out of hundred of function calls.
I am copying relevant parts of the app-shell.
Please help or at least point me in the right direction; I've been trying to fix this since 2 days. Thank you!
<app-location
route="{{route}}">
</app-location>
<app-route
route="{{route}}"
pattern=":view"
data="{{routeData}}"
tail="{{subroute}}">
</app-route>
<!-- Main content -->
<app-header-layout has-scrolling-region>
<app-header slot="header" class="main-header" condenses effects="waterfall">
<app-toolbar class="logo"></app-toolbar>
<app-toolbar class="tabs-bar" hidden$="{{!wideLayout}}">
<paper-tabs selected="[[selected]]" attr-for-selected="name">
<paper-tab>Home</paper-tab>
<paper-tab>About Us</paper-tab>
<paper-tab>Pricing</paper-tab>
</paper-tabs>
</app-toolbar>
</app-header>
<iron-pages
selected="[[page]]"
attr-for-selected="name"
fallback-selection="view404"
role="main">
<my-view1 name="home"></my-view1>
<my-view2 name="about"></my-view2>
<my-view3 name="pricing"></my-view3>
<my-view404 name="view404"></my-view404>
</iron-pages>
</app-header-layout>
</app-drawer-layout>
<script>
class MyApp extends Polymer.Element {
static get is() { return 'my-app'; }
static get properties() {
return {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged'
},
wideLayout: {
type: Boolean,
value: false,
observer: 'onLayoutChange'
},
items: {
type: Array,
value: function() {
return ['Home', 'About', 'Pricing', 'Adults', 'Contact'];
}
},
routeData: Object,
subroute: String,
// This shouldn't be neccessary, but the Analyzer isn't picking up
// Polymer.Element#rootPath
// rootPath: String,
};
}
static get observers() {
return [
'_routePageChanged(routeData.page)',
];
}
_routePageChanged(page) {
// If no page was found in the route data, page will be an empty string.
// Default to 'view1' in that case.
this.page = page || 'about';
console.log('_routePageChange');
// Close a non-persistent drawer when the page & route are changed.
if (!this.$.drawer.persistent) {
this.$.drawer.close();
}
}
_pageChanged(page) {
// Load page import on demand. Show 404 page if fails
var resolvedPageUrl = this.resolveUrl(page + '.html');
Polymer.importHref(
resolvedPageUrl,
null,
this._showPage404.bind(this),
true);
}
_showPage404() {
this.page = 'view404';
}
_onLayoutChange(wide) {
var drawer = this.$.drawer;
if (wide && drawer.opened){
drawer.opened = false;
}
}
}
window.customElements.define(MyApp.is, MyApp);
</script>
Here's a snapshot of the page when I click on the "Home" link.
Snapshot of the page
I have fixed the same issue on my app, page observed functions like:
static get properties() { return {
page:{
type:String,
reflectToAttribute:true,
observer: '_pageChanged'},
...
_pageChanged(page, oldPage) {
if (page != null) {
if (page === "home" ) {
this.set('routeData.page', "");
} else {
this.set('routeData.page', page);
}
.......
}
}
Honestly, I am still trying to find the better solution. Because I have users page and I could not manage to able to indexed at google search results. This only keeps synchronized the iron-pages and address link.