I am working on an existing SPA where we replace components with Aurelia components step by step. We use the enhance API of the TemplatingEngine. That works pretty well but we also need to tear down those enhanced fragments (remove event listeners, ...) when moving to another part of the application (no page reload).
My idea is to keep the aurelia instance in the page and reuse it.
Currently I enhance fragments like this:
function enhanceFragment(targetElement) {
function proceed() {
let aurelia = window.DFAurelia;
let engine = aurelia.container.get(TemplatingEngine);
engine.enhance({
container: aurelia.container,
element: targetElement,
resources: aurelia.resources
});
}
if (!window.DFAurelia) {
bootstrap(async aurelia => {
aurelia.use
.defaultBindingLanguage()
.defaultResources()
.eventAggregator()
.developmentLogging()
.globalResources('app/df-element');
await aurelia.start();
window.DFAurelia = aurelia;
proceed();
});
} else {
proceed();
}
}
The HTML I enhance looks like:
<df-element></df-element>
I tried this in a function of the custom element itself (DfElement::removeMyself()):
let vs: ViewSlot = this.container.get(ViewSlot);
let view: View = this.container.get(View);
vs.remove(view);
vs.detached();
vs.unbind();
but I get an error when getting the view from the container (Cannot read property 'resources' of undefined). I called this function from a click handler.
Main question: how to manually trigger the unbind and detached hooks of the DfElement and its children?
Bonus questions: properties of my aurelia instance (window.DFAurelia) root and host are undefined: is that a bad thing? Do you see any potential issue with this way of enhancing (and un-enhancing) fragments in the page?
Use the View returned from the enhance() method.
The enhance() method returns the enhanced View object. It is a good practice to manage the teardown from the same location where you call enhance(), as you may not be able to trust an element to remember to tear itself down. However, you can always register the View instance with the enhance container to access it from within the custom element.
function proceed() {
let aurelia = window.DFAurelia;
let container = aurelia.container;
let engine = container.get(TemplatingEngine);
let view = engine.enhance({
container: container,
element: targetElement,
resources: aurelia.resources
});
container.registerInstance(View, view);
}
This will tell the DI container to respond to calls for View with this View.
import { inject, Aurelia, View } from 'aurelia-framework';
#inject(Aurelia, Element)
export class DFCustomElement {
// element is passed to the constructor
constructor(aurelia, element) {
this.container = aurelia.container;
this.element = element;
}
// but View is only available after attached
attached() {
this.view = this.container.get(View);
}
removeMyself() {
this.element.remove();
this.view.detached();
this.view.unbind();
}
}
Using the created(view) lifecycle method
A much better practice would be to use the created(view) lifecycle method in your custom element.
import { inject } from 'aurelia-framework';
#inject(Element)
export class DFCustomElement {
constructor(element) {
this.element = element;
}
created(view) {
this.view = view;
}
removeMyself() {
this.element.remove();
this.view.detached();
this.view.unbind();
}
}
This is a much more straightforward, best practices way of having a custom element grab its own View. However, when trying to write this answer for you, I tested nesting a custom element in a <compose> element. The result was that the View in my custom element was actually the View for my <compose> element, and removeMyself() removed the <compose> entirely.
Related
I have a vue.js 3 application that needs to dynamically create components and access them.
The proper way to dynamically create them and injected them in the DOM seems to be th wrap them in an app using createApp, and this part works, when I call test() a new copy of the components appears at the bottom of the DOM.
Now I need to be able to get a reference to that component son I can call public (exposed) methods on it.
In my (actual) code below, what would allow me to get a reference to my component ?
import { createApp, h } from "vue";
import ConfirmModal from '#/views/components/ui/confirm-modal.vue';
function test()
{
let componentApp = createApp({
setup() {
return () => h(ConfirmModal, { title: "Fuck yeah!", type: "primary" });
}
});
const wrapper = document.createElement('div');
wrapper.setAttribute('id', 'confirm-modal-0');
componentApp.mount(wrapper);
document.body.appendChild(wrapper);
let _confirmModal: ConfirmModal = ???????????????????
_confirmModal.show();
}
EDIT : Here's my use-case: I have a helper function in a service which is used to confirm actions (like deletes) using a Modal dialog. This helper/service is pure TS, not a Vue component, but needs to instanciate the modal (which is a vue component), have it injected in the DOM and have its public methods callable (by my helper/service).
Right now, the only thing that works is to have a sungle copy of the modal component in my root layout and have to root layout foward the ref to my service, which can then use the component. But this isn't great because I need multiple instances of the dialog, and isn't good SOC to have to root layout handle a modal dialog.
I have a Nuxt.js site that I'm trying to get some fancy page transitions working on. I think I understand how you're supposed to use the transition setting when it's just CSS, but how do I make it reusable with JavaScript hooks?
It seems to me we should be able to do something like this:
// In a Page.vue template
transition(to, from) {
if (!from) {
return "fade"
}
if (to.name == "directors-name-work") {
// Animate to video playing
return "to-video"
}
if (from.name == "directors-name-work") {
// Scroll to slideshow, and at same video we just came from.
return "from-video"
}
}
And then I need to be able to define what the JS hooks are for to-video and from-video in JavaScript somewhere, but I have no idea where that goes? Where does enter() and beforeEnter() hooks get defined for the separate transitions? It makes sense if we just have one transition, then I could do it in a mixin. But when it is dynamic I have no idea.
Is there a file I should be putting somewhere called transition-to-video and transition-from-video?
It's currently undocumented, but the page's transition function can return a transition object, which may include the transition JavaScript hooks. This allows you to define your shared transition objects in a common file, and import them into a page as needed:
~/transitions.js:
export default {
fade: {
name: 'fade',
mode: 'out-in',
beforeEnter(el) {
console.log('fade beforeEnter')
}
},
bounce: {
name: 'bounce',
afterEnter(el) {
console.log('bounce afterEnter')
}
},
}
~/pages/about.vue:
<script>
import transitions from '~/transitions'
export default {
transition(to, from) {
return to.query.fade ? transitions.fade : transitions.bounce
},
}
</script>
I have a custom attribute with a method to show and hide some HTML content, I've attached the attribute to an element in a view model.
How can I call a method defined in the custom attribute from the view model?
To access the custom attribute's view-model, just put the custom attribute on the element a second time, but this time put .ref="viewModelPropertyName" on to the attribute. Then, in the parent view-model, you can access methods on the attribute using viewModelPropertyName (or whatever name you gave it). You can see an example of this here: https://gist.run/?id=9819e9bf73f6bb43b07af355c5e166ad
app.html
<template>
<require from="./has-method"></require>
<div has-method="hello" has-method.ref="theAttribute"></div>
<button click.trigger="callMethod()">Call method</button>
</template>
app.js
export class App {
callMethod() {
const result = this.theAttribute.someMethod('blah');
}
}
has-method.js
export class HasMethodCustomAttribute {
someMethod(foo) {
console.log('someMethod called with foo = ' + foo + ', this.value = ' + this.value);
return `Hello ${foo}`;
}
}
There are some ways to do it, but I believe the ideal would be binding a property from your custom-attribute to your view-model. For example:
MyCustomAttribute {
#bindable showOrHide; //use this to show or hide your element
}
MyViewModel {
visible = false;
}
Usage:
<div my-custom-attribute="showOrHide.bind: visible"></div>
So, whenever you change visible you will also change showOrHide.
Nevertheless, is good to remember that Aurelia already has a show and if custom-attributes:
<div show.bind="visible" my-custom-attribute></div>
<div if.bind="visible" my-custom-attribute></div>
Make sure if you really need to create this behaviour in your custom-attribute.
This can be done without the need for a ref. Here is an example that shows how.
It calls a showNotification method on the custom attribute from the custom element using the custom attribute.
In the custom attribute:
#bindable({ defaultBindingMode: bindingMode.twoWay }) showNotificationCallback: ()=> void;
bind() {
this.showNotificationCallback = this.showNotification.bind(this);
}
showNotification() {
// Your code here
}
In the custom element view (Note the absence of parens in the value of this binding):
<div notification="show-notification-callback.bind: showSaveSuccessNotification;></div>
In the custom element view-model:
// Show the save success view to the user.
if (typeof this.showSaveSuccessNotification=== 'function') {
this.showSaveSuccessNotification();
}
I have an application that is closely tied to the DOM. I need to keep track of the size and position of the elements that represent the objects behind them.
myViewModel.js
export class MyViewModel {
// my root view model has important properties
// that all other functions and objects need to use
constructor() {
this.importantProperty = 'veryimportant';
this.things = [];
}
// i create things in the view model that are
// represented in the dom
createThing() {
this.things.push({
isAThing: true
});
}
// i do things with things in the view model
// that depend strongly on the root view model
doSomethingWithThing(thing, property) {
thing[property] = `${this.importantProperty}${property}`;
}
// but i need to know all about the dom representation
// of the things in the view model
doAnotherThingWithThing(thing) {
console.log(`the height of the thing is ${thing.height}`);
}
lookAndSeeWhatSizeThisThingIs(element, thing) {
thing.height = element.clientHeight;
thing.width = element.clientWidth;
console.assert('That was easy!');
}
}
myViewModel.html
<template>
<!-- these things can change in size and shape, and I have
no idea what they will be until runtime
<div repeat.for="thing of things"
<!-- so ideally I'd like to call something like this -->
composed.delegate="lookAndSeeWhatSizeThisThingIs($element, thing)">
<img src="img/${$index}.png" />
</div>
</div>
Is there a way to do this today?
Since a CustomAttribute has access to the composition lifecycle, we can create a CustomAttribute that triggers an event on the element that fires in the attached() callback.
import {autoinject} from 'aurelia-framework';
#inject(Element)
export class AttachableCustomAttribute {
constructor(element) {
this.element = element;
}
attached() {
this.element.dispatchEvent(
new CustomEvent('attached'));
}
}
And use it just like any other event binding, with the exception that it does not bubble, and thus we must use trigger instead of delegate.
<div repeat.for="thing of things"
attached.trigger="lookAndSeeWhatSizeThisThingIs($event, thing)" attachable>
<img src="img/${$index}.png" />
</div>
In React Native I want to use global variables when I am moving between different screens
Can anyone help me how to achieve it?
The global scope in React Native is variable global. Such as global.foo = foo, then you can use global.foo anywhere.
But do not abuse it! In my opinion, global scope may used to store the global config or something like that. Share variables between different views, as your description, you can choose many other solutions(use redux,flux or store them in a higher component), global scope is not a good choice.
A good practice to define global variable is to use a js file. For example global.js
global.foo = foo;
global.bar = bar;
Then, to make sure it is executed when project initialized. For example, import the file in index.js:
import './global.js'
// other code
Now, you can use the global variable anywhere, and don't need to import global.js in each file.
Try not to modify them!
Try to use global.foo = bar in index.android.js or index.ios.js, then you can call in other file js.
You can use the global keyword to solve this.
Assume that you want to declare a variable called isFromManageUserAccount as a global variable you can use the following code.
global.isFromManageUserAccount=false;
After declaring like this you can use this variable anywhere in the application.
You can consider leveraging React's Context feature.
class NavigationContainer extends React.Component {
constructor(props) {
super(props);
this.goTo = this.goTo.bind(this);
}
goTo(location) {
...
}
getChildContext() {
// returns the context to pass to children
return {
goTo: this.goTo
}
}
...
}
// defines the context available to children
NavigationContainer.childContextTypes = {
goTo: PropTypes.func
}
class SomeViewContainer extends React.Component {
render() {
// grab the context provided by ancestors
const {goTo} = this.context;
return <button onClick={evt => goTo('somewhere')}>
Hello
</button>
}
}
// Define the context we want from ancestors
SomeViewContainer.contextTypes = {
goTo: PropTypes.func
}
With context, you can pass data through the component tree without having to pass the props down manually at every level. There is a big warning on this being an experimental feature and may break in the future, but I would imagine this feature to be around given the majority of the popular frameworks like Redux use context extensively.
The main advantage of using context v.s. a global variable is context is "scoped" to a subtree (this means you can define different scopes for different subtrees).
Do note that you should not pass your model data via context, as changes in context will not trigger React's component render cycle. However, I do find it useful in some use case, especially when implementing your own custom framework or workflow.
Set up a flux container
simple example
import alt from './../../alt.js';
class PostActions {
constructor(){
this.generateActions('setMessages');
}
setMessages(indexArray){
this.actions.setMessages(indexArray);
}
}
export default alt.createActions(PostActions);
store looks like this
class PostStore{
constructor(){
this.messages = [];
this.bindActions(MessageActions);
}
setMessages(messages){
this.messages = messages;
}
}
export default alt.createStore(PostStore);
Then every component that listens to the store can share this variable
In your constructor is where you should grab it
constructor(props){
super(props);
//here is your data you get from the store, do what you want with it
var messageStore = MessageStore.getState();
}
componentDidMount() {
MessageStore.listen(this.onMessageChange.bind(this));
}
componentWillUnmount() {
MessageStore.unlisten(this.onMessageChange.bind(this));
}
onMessageChange(state){
//if the data ever changes each component listining will be notified and can do the proper processing.
}
This way, you can share you data across the app without every component having to communicate with each other.
If you just want to pass some data from one screen to the next, you can pass them with the navigation.navigate method like this:
<Button onPress={()=> {this.props.navigation.navigate('NextScreen',{foo:bar)} />
and in 'NextScreen' you can access them with the navigation.getParam() method:
let foo=this.props.navigation.getParam(foo);
But it can get really "messy" if you have more than a couple of variables to pass..
The way you should be doing it in React Native (as I understand it), is by saving your 'global' variable in your index.js, for example. From there you can then pass it down using props.
Example:
class MainComponent extends Component {
componentDidMount() {
//Define some variable in your component
this.variable = "What's up, I'm a variable";
}
...
render () {
<Navigator
renderScene={(() => {
return(
<SceneComponent
//Pass the variable you want to be global through here
myPassedVariable={this.variable}/>
);
})}/>
}
}
class SceneComponent extends Component {
render() {
return(
<Text>{this.props.myPassedVariable}</Text>
);
}
}