Aurelia dynamic load - javascript

Is there a possibility with Aurelia to load a view with his viewmodel dynamically?
For example I have a list of modules, I click one of them and the target view will be loaded in the page. The list is not known a priori, so I can't load all modules.
model example.js
export class Modules{
list = [];
constructor(socket) {
socket.on('update', list => this.list);
}
select(module) {
//load and display the module view
}
}
view example.html
<template>
<ul>
<li repeat.for="module of list" click.delegate="$parent.select(module)">${module.name}</li>
</ul>
<div class="target">
<!-- Here should be loaded the module -->
</div>
</template>

Approach 1
Load the data in your app.js class's configureRouter method. Once the data is loaded you can configure the router. Just be sure to return a promise from configureRouter.
Approach 2
Use Aurelia's <compose> element to compose the selected module with the appropriate view.
Here's a working plunk
And the code:
app.js
import {inject} from 'aurelia-framework';
import {MockSocket} from './mock-socket';
#inject(MockSocket)
export class App {
list;
selectedModule;
constructor(socket) {
socket.on('update', list => this.list = list);
}
select(module) {
this.selectedModule = module;
}
}
app.html
<template>
<ul>
<li repeat.for="module of list">
<button type="button" click.delegate="$parent.select(module)">${module.path}</button>
</li>
</ul>
<div class="target">
<compose view-model.bind="selectedModule.path"></compose>
</div>
</template>

Related

Sharing reactive localStorage state between multiple vue apps on the same page

I'm creating a dashboard with Laravel and VueJS, I created a button that allows to enlarge or reduce my sidebar in a Sidebar.vue component here is my components:
<template>
<aside :class="`${is_expanded ? 'is-expanded' : ''}`">
<div class="head-aside">
<div class="app-logo">
<i class="bx bxl-trip-advisor"></i>
</div>
<span class="app-name">CONTROLPANEL</span>
</div>
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="ToggleMenu()">
<span class="boxicons"><i class="bx bx-chevrons-right"></i></span>
</button>
</div>
</aside>
</template>
<script>
export default {
data() {
return {
is_expanded: ref(localStorage.getItem('is_expanded') === 'true'),
}
},
methods: {
ToggleMenu() {
this.is_expanded = !this.is_expanded
localStorage.setItem('is_expanded', this.is_expanded)
},
},
}
</script>
The problem that arises is that in another component I created a navbar with a fixed width, what I would like to do is that when my sidebar changes size I would like my navbar to also change, in the other component I just have a template with a nav and the import of my sidebar.
You say you are using laravel with vue. It's not clear how the vue components are integrated, but I'm going to assume that laravel injects individual vue components that you would like to communicate. (as opposed to a vue based SPA that communicates with laravel using API only)
The two components are not aware of each other. Even though they both have access to the same localStorage, they don't know when the values there are updated.
There are several ways you can deal with this, here's one way
create a reactive object outside of the components to manage the shared state
import { ref, computed } from 'vue'
const isExpandedRef = ref(localStorage.getItem('is_expanded') === 'true');
window.IS_EXPANDED = computed({
get(){
return isExpandedRef.value
},
set(value){
isExpandedRef.value = !!value;
localStorage.setItem('is_expanded', isExpandedRef.value);
}
})
the ref is required so that when isExpandedRef.value changes, the computed getter triggers notifications to it's listeners.
If you load this before any of the components, you will create a computed(reactive) variable available to any script on the page (frames and shadow dom aside)
then you can use in your components like this.
<template>
<aside :class="`${is_expanded ? 'is-expanded' : ''}`">
<div class="head-aside">
<div class="app-logo">
<i class="bx bxl-trip-advisor"></i>
</div>
<span class="app-name">CONTROLPANEL</span>
</div>
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="is_expanded = !is_expanded">
<span class="boxicons"><i class="bx bx-chevrons-right"></i></span>
</button>
</div>
</aside>
</template>
<script>
export default {
data() {
return {
is_expanded: window.IS_EXPANDED,
}
},
}
</script>
because is_expanded is a computed with with getters and setters, you can set the value straight from the template, or use this.is_expanded = !this.is_expanded in methods.
As stated already, this relies on using the global window object. This solution is proposed for its simplicity. There are some drawbacks to using the window object, and a more robust solution would rely on injecting such shared state instead of relying on window, but it comes with more overhead.
This code works fine
<template>
<aside :class="`${is_expanded ? 'is-expanded' : ''}`">
<div class="head-aside">
<div class="app-logo">
<i class="bx bxl-trip-advisor"></i>
</div>
<span class="app-name">CONTROLPANEL</span>
</div>
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="ToggleMenu()">
<span class="boxicons">
<i class="bx bx-chevrons-right"></i>click me
</span>
</button>
</div>
</aside>
</template>
<script>
export default {
data() {
return {
is_expanded: localStorage.getItem("is_expanded") === "true",
};
},
methods: {
ToggleMenu() {
this.is_expanded = !this.is_expanded;
localStorage.setItem("is_expanded", this.is_expanded);
},
},
};
</script>
Here is a GIF showing the result in action: https://share.cleanshot.com/TtKsUj
Make local storage reactive by using watcher and setting its deep property to true
https://vuejs.org/guide/essentials/watchers.html#deep-watchers
or if you are using vue2 then use event bus

Each child in a list should have a unique key prop problem

Good day,
i'm experiencing a slight problem. i want to have productdetails page where all the info is being displayed of the product. You can do that through clicking on the product link. but i'm getting the following err: Each child in a list should have a unique key prop problem
code homepage:
import React from 'react';
import data from '../data'
import { Link } from 'react-router-dom';
function Homescreen (props){
return <ul className="products" >
{ data.products.map(product =>
<li key={product}>
<div className="product-card">
<Link to={'/product/' + product._id}>
<img className="product-image" src={product.image} alt="product"></img>
</Link>
<Link to={'/product/' + product._id}><h1 className="product-name">{product.name}</h1></Link>
<p className="product-price">€{product.price}</p>
<div className="product.rating">Stars ({product.numReviews})</div>
<form method="post">
<button type="submit">Add to cart</button>
</form>
</div>
</li>
)
}
</ul>
}
export default Homescreen;
code productpage:
import React from 'react';
import data from '../data';
import { Link } from 'react-router-dom';
function productScreen (props){
console.log(props.match.params.id)
const product = data.products.find(x=> x._id === props.match.params.id)
return <div>
<h1>product</h1>
<div>
<Link to="/">Back to products</Link>
</div>
<div className="productdetails">
<div className="details-image">
<img src={product.image} alt="product"></img>
</div>
<h1>{product.name}</h1>
<h4>{product.price}</h4>
<span>{product.rating} Stars ({product.numReviews} Reviews)</span>
</div>
</div>
}
export default productScreen;
As stated on the ReactJS Website
A “key” is a special string attribute you need to include when creating lists of elements.
Therefore, a key can not be an object like the way you are using it here and will need to be a String. If your product does not have a unique identifier, then you can choose to use the index of your map method.
When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort:
// Example from the ReactjS Website
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
You should change:
<li key={product}>
To (assuming your id is unique)
<li key={product._id}>
The best way to pick a key is to use a string that uniquely identifies a list item among its siblings, you can check more here

Vue.js referenced template components stop after first sub component

I'm attempting to create components using Vue, so that I can remove a lot of duplicated HTML in a site I'm working on.
I have a <ym-menucontent> component, which within it will eventually have several other components, conditionally rendered.
While doing this I've hit a wall and so have simplified everything to get to the root of the problem.
When rendering the ym-menucontent component the first sub-component is the only one which gets rendered and I can't work out why or how to get around it...
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"/>
<ym-rootmaps :menuitem="menuitem"/>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<template id="rootmaps">
<div>Root Maps</div>
</template>
<template id="categories">
<div>Categories</div>
</template>
app.js
Vue.component('ym-menucontent', {
template: '#menucontent',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON
}
}
});
Vue.component('ym-rootmaps', {
template: '#rootmaps',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
Vue.component('ym-categories', {
template: '#categories',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
usage...
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"/>
</div>
Output
<div>Categories</div>
if I switch around ym-cateogries and ym-rootmaps then the output becomes...
<div>Root Maps</div>
if I remove both then I see...
<p>1: true</p>
<p>2:</p>
I'd expect to see a combination of all of them...
<div>Categories</div>
<div>Root Maps</div>
<p>1: true</p>
<p>2:</p>
This is probably because you're using self-closing components in DOM templates, which is recommended against in the style-guide ..
Unfortunately, HTML doesn’t allow custom elements to be self-closing -
only official “void” elements. That’s why the strategy is only
possible when Vue’s template compiler can reach the template before
the DOM, then serve the DOM spec-compliant HTML.
This should work for you ..
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"></ym-categories>
<ym-rootmaps :menuitem="menuitem"></ym-rootmaps>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"></ym-menucontent>
</div>

Can I assign a click event or <A> on a whole Ember component?

I have an Ember component with tagName:'li'
The template looks like so:
<div> title</div>
<div> image </div>
<div> text </div>
The outcome is <li> blocks of the above elements, like so:
<li id="ember2484" class="...">
<div>...</div>
<div>...</div>
<div>...</div>
</li>
I need to make the <li> clickable somehow, because I have a link url that I want to assign to each <li> element.
Is that possible, to make <li> clickable?
Here is a small twiddle demonstrating how to do this
Documentation here
import Component from '#ember/component';
export default class extends Component {
clickCounter = 0;
click() {
this.set('clickCounter', this.clickCounter + 1);
}
}
// or in the old syntax
import Component from '#ember/component';
export default Component.extend({
clickCounter: 0,
click() {
this.set('clickCounter', this.clickCounter + 1);
}
});

Materialize Css Parallex is not a function

I am trying to use the parallax within a react component. I have gotten this to work in the past. This is a MeteorJs project as well
I get a console error:
$(...).parallax is not a function
My component:
import React from 'react';
import $ from 'jquery';
export default class Index extends React.Component{
render(){
$(document).ready(function(){
$('.parallax').parallax();
});
return(
<div className="container">
<div className="parallax-container">
<div className="parallax"><img src="images/parallax1.jpg"/></div>
</div>
<div className="section white">
<div className="row container">
<h2 className="header">Parallax</h2>
<p className="grey-text text-darken-3 lighten-3">Parallax is an effect where the background
content or image in this case, is moved at a different speed than the foreground content while
scrolling.</p>
</div>
</div>
<div className="parallax-container">
<div className="parallax"><img src="images/parallax2.jpg"/></div>
</div>
</div>
)
}
}
My client main.js file:
import '../imports/startup/client/routes';
import '../node_modules/materialize-css/dist/js/materialize.min';
import '../node_modules/materialize-css/js/parallax';
The error message is telling you that .parallax() isn't a function in this line of code:
``
$('.parallax').parallax();
```
Which means that $('.parallax') is returning an object (usually a html element). It is not surprising that .parallax() is not a function, because it's just a html element.
Looking at the documentation, I think you are missing this initialisation code:
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.parallax');
var instances = M.Parallax.init(elems, options); });

Categories