how to translate only part of html page with i18n - aurelia

i trying to build logic to translate only part of the page(module) with i18n lib.
i have set i18n globally to change language on page when i change language, but i would like to have one module on that page (like some kind of preview for email) on different language which i can change on that module via some dropdown field. Like some kind of scoped i18n.
I'm using aurelia-i18n 1.4.0 version.
is it possible to set <span t="messages.hello_message">hello<span> to watch for local module changes for language and not the global one, but again to use the same translation files as global does.
Did anyone have some similar problem or idea how can I do this? thanks. :)

You can't do that out of the box. When you change the active locale using setLocale the method fires an event and signals an update to binding behaviors https://github.com/aurelia/i18n/blob/master/src/i18n.js#L54.
The TCustomAttribute listens for those changes and automatically rerenders bindings. What you could do though is create your own custom attribute as seen here https://github.com/aurelia/i18n/blob/master/src/t.js and override the bind and unbind methods where you define the condition when the update of translations should happen.
--- Update with example ---
Ok so here's a small example what I was thinking about, might not be the nicest way but it should do it.
In your main.js add a new globalResources
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.plugin('aurelia-i18n', (instance) => {
...
})
.globalResources("./foo-custom-attribute") // <-- this here
now create a file foo-custom-attribute.js
import {TCustomAttribute} from 'aurelia-i18n';
import {customAttribute} from 'aurelia-framework';
#customAttribute('foo')
export class FooCustomAttribute extends TCustomAttribute {
constructor(element, i18n, ea, tparams) {
super(element, i18n, ea, tparams);
}
bind() {
this.params = this.lazyParams();
if (this.params) {
this.params.valueChanged = (newParams, oldParams) => {
this.paramsChanged(this.value, newParams, oldParams);
};
}
let p = this.params !== null ? this.params.value : undefined;
this.service.updateValue(this.element, this.value, p);
}
unbind() {}
}
This essentially creates a new attribute called foo, which extends the TCustomAttribute and overrides the bind/unbind methods to exclude the signaling and listening to language changed events.
In your view you can now use
<span t="demo"></span>
<span foo="demo"></span>
Toggling the language now will change the t attribute as usual but will keep the foo as it is.

Related

Vue.js Create a helper class to call your methods globally

I have just started my first project with Vue.js, I have managed to do a lot of basic things and now I am trying to structure the project. I want to achieve the highest possible code reuse. One of the most frequent cases of my application is going to be showing messages of different types, confirmation, information, etc. For this reason, I want to create a mechanism that allows me to launch these messages globally, regardless of where I call them.
As far as I have been able to advance, I have opted for the following variant:
1- I have created a directory called classes in my src directory.
2- I have created a file called MessageBox.js inside classes directory with the following content:
import Vue from 'vue';
export default class MessageBox extends Vue {
confirm() {
return alert('Confirm');
}
information() {
return alert('Information');
}
}
I define it like this because I want to call these methods globally as follows:
MessageBox.confirm();
I am really new to Vue.js and I was wondering if there is any other way to achieve the results I am looking for in a more efficient way .... or .. maybe more elegant?
Thank you very much in advance..
There are at least 2 ways of going about this:
Event bus
Rely on Vue.js internals to create a simple EventBus. This is a design pattern used in Vue.js.
Create a file and add the following lines to it
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
Create your component that takes care of displaying global dialogs. This is usually registered at the top of the tree, so it can cover the entire real estate.
Import the event bus import EventBus from 'event_bus' and then register for the new events
EventBus.$on('SHOW_CONFIRM', (data) => {
// business logic regarding confirm dialog
})
Now you can import it in any component that wants to fire an event like so
EventBus.$emit('SHOW_CONFIRM', confirmData);
Vuex
You can also use vuex to store global data regarding dialogs and add mutations to trigger the display of the dialogs.
Again, you should define a component that takes care of displaying and push it towards the top of the visual tree.
Note: in both cases you should handle cases in which multiple dialog need to be shown at the same time. Usually using a queue inside the displaying component works.
It's an antipattern in modern JavaScript to merge helper functions that don't rely on class instance into a class. Modules play the role of namespaces.
Helper functions can be defined as is:
messageBox.js
export function confirm() {
return alert('Confirm');
}
They can be imported and used in component methods. In case they need to be used in templates, they can be assigned to methods where needed one by one:
Some.vue
import { confirm } from './util/messageBox';
export default {
methods: { confirm }
}
Or all at once:
import * as messageBox from './util/messageBox';
export default {
methods: { ...messageBox }
}
Helpers can be also be made reusable as Vue mixins:
messageBox.js
...
export const confirmMixin = {
methods: { confirm };
}
export default {
methods: { confirm, information };
}
And used either per component:
Some.vue
import { confirmMixin } from './util/messageBox';
export default {
mixins: [confirmMixin]
}
Or globally (isn't recommended because this introduces same maintenance problems as the use of global variables):
import messageBoxMixin from './util/messageBox';
Vue.mixin(messageBoxMixin);

vue.js: scoping custom option merge strategies instead going global

Good Day Fellows,
Quick summary: how can I use custom option merge strategies on an individual basis per component and not globaly?
My problem:
I am extending my components via Mixins and it is working great so far. However, while it is working great with the likes of component methods, I often need to override some lifecycle hooks, like mounted, created, etc. The catch is, Vue - by default - queues them up in an array and calls them after another. This is of course defined by Vues default merge strategies.
However in some specific cases I do need to override the hook and not have it stack. I know I can customize Vue.config.optionMergeStrategies to my liking, but I want the mergeStrategy customized on a per component basis and not applying it globably.
My naive approach on paper was to create a higher function which stores the original hooks, applies my custom strategy, calls my component body and after that restores Vues original hooks.
Let's say like this
export default function executeWithCustomMerge(fn) {
const orig = deep copy Vue.config.optionMergeStrategies;
Vue.config.optionMergeStrategies.mounted = (parent, child) => [child];
fn();
Vue.config.optionMergeStrategies = deep copy orig;
}
And here's it in action
executeWithCustomMerge(() => {
Vue.component('new-comp', {
mixins: [Vue.component("old-comp")],
},
mounted() {
//i want to override my parent thus I am using a custom merge strategy
});
});
Now, this is not going to work out because restoring the original hook strategies still apply on a global and will be reseted before most hooks on my component are being called.
I wonder what do I need to do to scope my merge strategy to a component.
I had a look at optionMergeStrategies in more detail and found this interesting quote from the docs (emphasis mine):
The merge strategy receives the value of that option defined on the parent and child instances as the first and second arguments, respectively. The context Vue instance is passed as the third argument.
So I thought it would be straightforward to implement a custom merging strategy that inspects the Vue instance and looks at its properties to decide which strategy to use. Something like this:
const mergeCreatedStrategy = Vue.config.optionMergeStrategies.created;
Vue.config.optionMergeStrategies.created = function strategy(toVal, fromVal, vm) {
if (vm.overrideCreated) {
// If the "overrideCreated" prop is set on the component, discard the mixin's created()
return [vm.created];
}
return mergeCreatedStrategy(toVal, fromVal, vm);
};
It turns out though that the 3rd argument (vm) is not set when the strategy function is called for components. It's a new bug! See https://github.com/vuejs/vue/issues/9623
So I found another way to inform the merge strategy on what it should do. Since JavaScript functions are first-class objects, they can have properties and methods just like any other object. Therefore, we can set a component's function to override its parents by setting a property on it and looking for its value in the merge strategy like so:
Vue.mixin({
created() {
this.messages.push('global mixin hook called');
}
});
const mixin = {
created() {
this.messages.push('mixin hook called');
},
};
const mergeCreatedStrategy = Vue.config.optionMergeStrategies.created;
Vue.config.optionMergeStrategies.created = function strategy(toVal, fromVal) {
if (fromVal.overrideOthers) {
// Selectively override hooks injected from mixins
return [fromVal];
}
return mergeCreatedStrategy(toVal, fromVal);
};
const app = {
el: '#app',
mixins: [mixin],
data: { messages: [] },
created() {
this.messages.push('component hook called');
},
};
// Comment or change this line to control whether the mixin created hook is applied
app.created.overrideOthers = true;
new Vue(app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>Messages from hooks</h1>
<p v-for="message in messages">{{ message }}</p>
</div>

Access the component name from a Vue directive

I want to create a custom Vue directive that lets me select components on my page which I want to hydrate. In other words, this is what I want to archive
I render my Vue app on the server (ssr)
I attach a directive to some components, like this:
<template>
<div v-hydrate #click="do-something"> I will be hydrated</div>
</template>
I send my code to the client and only those components that have the v-hydrate property will be hydrated (as root elements) on the client.
I want to achieve this roughly this way:
I will create a directives that marks and remembers components:
import Vue from "vue";
Vue.directive("hydrate", {
inserted: function(el, binding, vnode) {
el.setAttribute("data-hydration-component", vnode.component.name);
}
});
My idea is that in my inserted method write a data-attribute to the server-rendered element that I can read out in the client and then hydrate my component with.
Now I have 2 questions:
Is that a feasible approach
How do I get the component name in el.setAttribute? vnode.component.name is just dummy code and does not exist this way.
PS: If you want to know why I only want to hydrate parts of my website: It's ads. They mess with the DOM which breaks Vue.
I could figure it out:
import Vue from "vue";
Vue.directive("hydrate", {
inserted: function(el, binding, vnode) {
console.log(vnode.context.$options.name); // the component's name
}
});
I couldn't get the name of my single file components using the previously posted solution, so I had a look at the source code of vue devtools that always manages to find the name. Here's how they do it:
export function getComponentName (options) {
const name = options.name || options._componentTag
if (name) {
return name
}
const file = options.__file // injected by vue-loader
if (file) {
return classify(basename(file, '.vue'))
}
}
where options === $vm.$options

VueJS reactive binding to module export

I'm new to Vue and I'm trying to bind a component value to a property of an exported object. The initial value is set correctly but it's not reactive. I'm not sure I'm using the right terminology, but the relevant sections are
// Settings.js
export const settings = { showOverlay: true }
// Overlay.vue
<template>
<div v-show="enabled"> Some stuff </div>
</template>
<script>
import { settings } from "../js/Settings.js";
export default {
data() {
return {
enabled: settings.showOverlay
};
}
};
</script>
Now, I know that the exported object (settings) is a read-only view onto the object, because that's how modules work, so probably Vue can't put its hooks into it. The thing is, I want the setting to be "owned" by this Settings service, which is responsible for persisting the values between page loads, but I don't feel like the service should have to be aware that the component wants to watch a value and take care of manually triggering updates on the component when the value changes -- I probably just misunderstand the pattern I'm supposed to use for cases like this.
This is being built with Webpack / babel, if that makes any difference.
I'm feeling a little bit sheepish at the moment. I went down a little rabbit hole based on some syntax I saw in your question and that let to a whole bunch of unnecessary gyrations. The syntax was this:
data() {
return {
enabled: settings.showOverlay
};
}
Which, for some reason, I interpreted as "well sure, whenever enabled changes, settings.showOverlay is going to change because Vue is reactive".
Yeah, no.
In that code, settings.showOverlay is just the initial value for the enabled property. The enabled property will be reactive, but in no way is it going to pass values to the settings object. Basically the data function returns an object with an enabled property that has an initial value of whatever settings.showOverlay is and then that object is turned into a reactive object.
If you want the changes made in Vue to be passed along to your settings object then all you need to do is expose the settings object on Vue's data object.
data() {
return {
settings,
};
}
Now if you have code like
<div v-show="settings.showOverlay"> Some stuff </div>
<button #click="settings.showOverlay= !settings.showOverlay"></button>
settings.showOverlay will not only be reactive in the Vue, but in the settings object. No need for any of the hoops I jumped through below (/facepalm).
FWIW I believe some of the links I mentioned in the comments are referring to the data object itself. The data object needs to be a plain javascript object, not necessarily all the properties on it.
In other words, in
data() {
return something
}
something must be a plain javascript object.
Original Answer
I've done this in a couple ways in my Vue apps. In my first app I wanted to do the same thing, store the settings in an external module that could manage persisting the settings and expose those settings on my Vue. I ended up writing a class that looks like this.
class Settings {
constructor(){
// read settings from persisted solution
}
get(key){
// return "key" from settings
}
set(key){
// set "key" in settings
}
save(){
// save settings to persisted solution
}
}
export default Settings
And then used that in my Vue like this.
import Settings from "./settings"
new Vue({
data:{
someSetting: Settings.get("someSetting")
}
})
And then some point later, trigger set() and save(). That point for me was whenever a route change was triggered, I'd just set all the settings back to the Settings object and then save.
It sounds like what you have is you're exporting an object that has getter/setter properties possibly something like this.
export const settings = {
overlay: stored.showOverlay,
get showOverlay(){
return this.overlay
},
set showOverlay(v){
this.overlay = v
}
}
Where you maybe trigger a save when set is triggered. I like that idea better than the solution I described above. But getting it to work is a little more work. First I tried using a computed.
new Vue({
computed:{
showOverlay: {
get(){ return settings.showOverlay }
set(v) { settings.showOverlay = v }
}
}
})
But that doesn't quite work because it doesn't reflect changes to the Vue. That makes sense because Vue doesn't really know the value changed. Adding a $forceUpdate to the setter doesn't work either, I expect because of the caching nature of computed values. Using a computed in combination with a data property, however, does work.
new Vue({
data(){
return {
showOverlay_internal: settings.showOverlay
}
},
computed:{
showOverlay: {
get(){ return this.showOverlay_internal }
set(v) {
settings.showOverlay = v
this.showOverlayInternal = v
}
}
}
})
That changes both the state of the Vue and triggers the change in the settings object (which in turn can trigger persisting it).
But, damn, that's a lot of work.
It's important to remember sometimes, though, that the objects we use to instantiate Vue are just plain old javascript objects and we can manipulate them. I wondered if I could write some code that creates the data property and the computed value for us. Taking a cue from Vuex, yes we can.
What I ended up with was this.
import {settings, mapSetting} from "./settings"
const definition = {
name:"app"
}
mapSetting(definition, "showOverlay"
export default definition
mapSetting does all the work we did above for us. showOverlay is now a computed property that reacts to changes in Vue and updates our settings object. The only drawback at the moment is that it exposes a showOverlay_internal data property. I'm not sure how much that matters. It could be improved to map multiple properties at a time.
Here is the complete code I wrote that uses localStorage as a persistence medium.
function saveData(s){
localStorage.setItem("settings", JSON.stringify(s))
}
let stored = JSON.parse(localStorage.getItem("settings"))
if (null == stored) {
stored = {}
}
export const settings = {
overlay: stored.showOverlay,
get showOverlay(){
return this.overlay
},
set showOverlay(v){
this.overlay = v
saveData(this)
}
}
function generateDataFn(definition, setting, internalName){
let originalDataFn = definition.data
return function(){
let data = originalDataFn ? originalDataFn() : {}
data[internalName] = settings[setting]
return data
}
}
function generateComputed(internalName, setting){
return {
get(){
return this[internalName]
},
set(v){
settings[setting] = v
this[internalName] = v
}
}
}
export function mapSetting(definition, setting){
let internalName = `${setting}_internal`
definition.data = generateDataFn(definition, setting, internalName)
if (!definition.computed)
definition.computed = {}
definition.computed[setting] = generateComputed(internalName, setting)
}

How to use global variables in React Native?

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>
);
}
}