I am creating a vue webapp, I have few pages with Dynamic content and also few pages which has mostly static content. I want to move all these static strings to one place.
One option can be to use vue-i18n or vue-multilanguage, these gives support to have content files like this, but I really have no use case of support of multiple languages, so it also seems a bit over kill to me.
Another option can be to have a vuex store for all the strings, vuex I am already using for state management.
What can be good approach to do this.
I am not aware of a standard way of doing this, also this would be applicable to all the web frameworks. That said it is an interesting and valid problem.
If I had to do something about it:
I would want these strings to be available everywhere.
I would prefer not having to import these strings in all the components and each time I needed to use them.
I would want the storage space to be descriptive so that I don't have to go back and forth to check what I want to import. [The toughest part in my opinion]
To achieve 1, we can use:
Vuex
A services/some.js file which exports an object.
Plugins
I would go with plugins because:
I can get the strings by merely using this in a component, Vue.use(plugin) prevents the same plugin getting used twice, and at the same time achieve all the points (3rd will still be a tough nut to crack). Only disadvantage that I know of it might clutter the vue-instance.
So plugin can be designed like:
// stringsHelperPlugin.js
const STRING_CONST = {
[component_1_Name]: {
key1: val1,
key2: val2,
....
},
[component_2_Name]: {
key1: val1,
key2: val2,
....
},
...
}
StringConst.install = function (Vue, options) {
Vue.prototype.$getStringFor = (componentName, key) => {
return STRING_CONST['componentName'][key]
}
}
export default StringConst
in main.js this can be used like:
import StringConst from 'path/to/plugin'
Vue.use(StringConst)
and you could use this in a component template like so:
<div>
{{ $getStringFor(<component_1_name>, 'key1') }}
</div>
You can use something like this.$getStringFor(<componentName>, key) in a method. Pretty much everything that vuejs to has to offer.
Why I call the 3rd point hardest is: Maintainance if you ever change component names, you might also have to change it in the object returned by the plugin. This problem again, can be handled in many ways.
You can make an npm module with JSON files containing your strings
If you don't use vuex in your project, put your content in some javascript files which will be basically objects with all your static content and import them where you need just like Belmin menionted I am using Vue js and python flask as my backend. I want to have some local variable set. How can it be done?
A similar approach can be used for urls, configurations, errors etc.
If you use vuex, centralize everything there and make getters which you can use in each of your components.
Related
I'm working on a React frontend that gets data from a python JSON API. One section of my website has premium content and is reserved for paying users; currently I ensure that other users don't have access to it by fetching the content as a JSON object and converting it to JSX on the frontend according to a certain convention. For example:
{
{ 'type': 'paragraph', 'value': 'some text'},
{ 'type': 'anchor', 'href': 'some url', 'value': 'some description'}
}
would be rendered as :
<p>some text</p>
some description
Not surprisingly, things started to get pretty complicated as the content began to get more structured, simple things like making part of the text bold require a disproportional amount of effort.
As a potential solution, I had this idea: instead of sending the content as an object and parsing it, why not send a string of JSX and evaluate it on the frontend?
I started like this:
import * as babel from "#babel/standalone";
export function renderFromString(code) {
const transformed_code = babel.transform(code, {
plugins: ["transform-react-jsx"]
}).code;
return eval(transformed_code);
}
I imported this function in my premiumContent page and tried passing a complete component as a string (with import statements, etc) but got errors because the modules can't be found. I assumed this happens because the code is being interpreted by the browser so it doesn't have access to node_modules?
As a workaround, I tried passing only the tags to renderFromString and call it in the context of my component where all the modules are already imported :
import * as babel from "#babel/standalone";
export function renderFromString(code, context) {
const _es5_code = babel.transform(code, {
plugins: ["transform-react-jsx"]
}).code;
return function() {
return eval(_es5_code);
}.call(context);
}
This also failed, because it seems that eval will still run from the local context.
Finally, I tried doing the same as above but executing eval directly in my component, instead of from my function .This works as a long as I store "React" in a variable : import ReactModule from "react";const React = ReactModule, otherwise it can't be found.
My questions are:
Is there any way I can make my first two approaches work?
I know eval is considered harmful, but since the content is always completely static and comes from my own server, I don't see how this wouldn't be safe. Am I wrong?
Is there a better solution for my problem? That is, a way to safely deliver structured content to only some users without changing my single page app + JSON api setup?
The best solution for this is React server-side rendering.
Since you need markup that is client-side compatible but at the same time dynamically generated through React, you can offload the markup generation to the server. The server would then send the rendered HTML to the client for immediate display.
Here's a good article about React SSR and how it can benefit performance.
I'm using Redux in a vanilla JS project. I have a bunch of small modular UI files and controllers and such. In those UI files I might have code like:
const ExampleForm = function (StoreInstance) {
return $('<form />', {
submit: () => {
StoreInstance.dispatch({
type: 'EXAMPLE_DISPATCH',
post: {
message: $TextareaComponent.val()
}
})
return false
}
})
}
The issue is I have a lot of simple view files like this and many of them are nested and I'm finding it to be ugly and error prone to have the store passed as a param to everything.
For example, I trimmed it for brevity but the form component has form element components such as a textarea. Currently I see two options of managing the Store:
Setting it to window when creating it in my entry file (index.js) and then just accessing Store globally. This seems the nicest, although not "best practice" and makes unit testing and server side rendering a bit harder.
Passing it to every component tediously. This is my example above. This I'd consider as "best practice" but it's pretty annoying to do for every file you make almost.
I'm wondering if there's any alternatives or tricks to passing the store instance. I'm leaning towards just making it global.
You could use the constructor pattern and create every view as new ConnectedView(). The ConnectedView would have a memoized instance of the store (this.store within the view), so it doesn't need to be global.
I just started using Sapper (https://sapper.svelte.technology) for the first time. I really like it so far. One of the things I need it to do is show a list of the components available in my application and show information about them. Ideally have a way to change the way the component looks based on dynamic bindings on the page.
I have a few questions about using the framework.
First, I'll provide a snippet of my code, and then a screenshot:
[slug].html
-----------
<:Head>
<title>{{info.title}}</title>
</:Head>
<Layout page="{{slug}}">
<h1>{{info.title}}</h1>
<div class="content">
<TopBar :organization_name />
<br>
<h3>Attributes</h3>
{{#each Object.keys(info.attributes) as attribute}}
<p>{{info.attributes[attribute].description}} <input type="text" on:keyup="updateComponent(this.value)" value="Org Name" /></p>
{{/each}}
</div>
</Layout>
<script>
import Layout from '../_components/components/Layout.html';
import TopBar from '../../_components/header/TopBar.html';
let COMPONENTS = require('../_config/components.json');
export default {
components: {
Layout, TopBar
},
methods: {
updateComponent(value) {
this.set({organization_name: value});
}
},
data() {
return {
organization_name: 'Org Name'
}
},
preload({ params, query }) {
params['info'] = COMPONENTS.components[params.slug];
return params;
}
};
</script>
Now my questions:
I notice I can't #each through my object. I have to loop through its keys. Would be nice if I could do something like this:
{{#each info.attributes as attribute }}
{{attribute.description}}
{{/each}}
Before Sapper, I would use Angular-translate module that could do translations on strings based on a given JSON file. Does anyone know if a Sapper/Svelte equivalent exists, or is that something I might need to come up with on my own?
I'm not used to doing imports. I'm more use to dependency injection in Angular which looks a bit cleaner (no paths). Is there some way I can create a COMPONENTS constant that could be used throughout my files, or will I need to import a JSON file in every occurence that I need access to its data?
As a follow-up to #3, I wonder if there is a way to better include files instead of having to rely on using ../.. to navigate through my folder structure? If I were to change the path of one of my files, my Terminal will complain and give errors which is nice, but still, I wonder if there is a better way to import my files.
I know there has got to be a better way to implement what I implemented in my example. Basically, you see an input box beside an attribute, and if I make changes there, I am calling an updateComponent function which then does a this.set() in the current scope to override the binding. This works, but I was wondering if there was some way to avoid the function. I figured it's possible that you can bind the value of the input and have it automatically update my <TopBar> component binding... maybe?
The preload method gives me access to params. What I want to know if there is some way for me to get access to params.slug without the preload function.
What would be really cool is to have some expert rewrite what I've done in the best possible way, possibly addressing some of my questions.
Svelte will only iterate over array-like objects, because it's not possible to guarantee consistent behaviour with objects — it throws up various edge cases that are best solved at an app level. You can do this sort of thing, just using standard JavaScript idioms:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} ...</p>
{{/each}}
<!-- or, if you need the key as well -->
{{#each Object.entries(info.attributes) as [key, value]}}
<p>{{attr.description}} ...</p>
{{/each}}
Not aware of a direct angular-translate equivalent, but a straightforward i18n solution is to fetch some JSON in preload:
preload({ params, query }) {
return fetch(`/i18n/${locale}.json`)
.then(r => r.json())
.then(dict => {
return { dict };
});
}
Then, you can reference things like {{dict["hello"]}} in your template. A more sophisticated solution would only load the strings necessary for the current page, and would cache everything etc, but the basic idea is the same.
I guess you could do this:
// app/client.js (assuming Sapper >= 0.7)
import COMPONENTS from './config/components.json';
window.COMPONENTS = COMPONENTS;
// app/server.js
import COMPONENTS from './config/components.json';
global.COMPONENTS = COMPONENTS;
Importing isn't that bad though! It's good for a module's dependencies to be explicit.
You can use the resolve.modules field in your webpack configs: https://webpack.js.org/configuration/resolve/#resolve-modules
This would be a good place to use two-way binding:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} <input bind:value=organization_name /></p>
{{/each}}
Yep, the params object is always available in your pages (not nested components, unless you pass the prop down, but all your top-level components like routes/whatever/[slug].html) — so you can reference it in templates as {{params.slug}}, or inside lifecycle hooks and methods as this.get('params').slug, whether or not a given component uses preload.
I would like to share a common url between different components in vuejs
So in my main.js
Vue.prototype.$apiurl = '127.0.0.1:8000/api'
Now in my component i would like to call
console.log("apiurl is", this.$apiurl );
The above doesnt work. How do access global variables in vuejs
Having a dictionary for keeping constants seems more appropriate to me in this case, something like this:
APP_CONSTS = {
API_URL: "127.0.0.1:8000/api"
}
Then accessing it whenever you need, like APP_CONSTS.API_URL.
In some situations you may need to share data between components, in these cases state management comes into play. But it may be an overkill for your case.
It may be a very dumb question... I am using Meteor-ui-progress-circle and I want redrawing the template when the percentage (wich is store in a reactive collection Progress) is changed (currently, when I click on a "play" button).
I think I have to use Blaze.render but I don't really understand how it work.
Here a part of my main template (in Jade) :
div.panel-body
div.col-md-9.col-sm-8
p Lorem ipsum...
div.col-md-3.col-sm-4#progress-circle
+progressCircle progress="0" radius="100" class="green"
And my JavaScript :
Template.controlBar.events(
{
"click .play-button": function ()
{
var tmp = Progress.findOne({});
if (!tmp)
{
Meteor.call('createProgress');
tmp = Progress.findOne({});
}
var val = tmp.progressValue;
val += 10;
if (val > 100)
return;
Meteor.call('updateProgess', tmp._id, val);
Template.progressCircle.progress = tmp.progressValue;
Blaze.render(Template.progressCircle, $("#progress-circle")[0]);
},
Doing this... I have several template that are displaying each time I click on the play button. I don't understand how to specify that I don't want a new template but just re-render the one I already have.
Not sure I quite understand your question, but I'll try to help by giving my best understanding of templating and how I have come to use them. If someone sees any incorrect information here, please speak up so I can get a better understanding myself and correct this answer.
First, the Template.XXX.events handlers. In your event handler, you are using a function with no arguments. You can actually accept 2 arguments for these event handler functions: the event and the template. So, you can do something like thus:
Template.controlBar.events({
'click .play_button': function(event, tmpl) {
tmpl.$('div#progress-circle').doSomething();
}
});
Notice the tmpl.$() call? That says to use jQuery to find the specified selector, but ONLY in the current template. This is a wonderful way to use classes to generalize your components, but then be able to filter the selection to only those within the same template...
...Which brings me to my next bit of advice: Use child templates excessively. Any component that I can identify as an "autonomous component" on my page I will consider as a separate template. For instance, I was recently working on a custom reporting page that had a table and some D3 graphs representing some real-time data. In this report page, I had one main template defined for the "page", then each of the D3 graphs where defined as a separate template, and the table was another separate template. This allows several advantages:
Compartmentalization of the "components" of the page, allowing code reuse (I can now put the same graph on ANY page, since it's now an autonomous "component"
The advantage of using the Template.XXX.events trick above to "narrow" the scope of my element searches to elements within that template
Prevents total page refreshes as Meteor is smart enough to only refresh templates that need to be refreshed, which also speeds the responsiveness of the page itself
As a result, I try to apply my Templates liberally. In your case, it would sound to me that if I were to have multiply progress bars on the page that I might turn those into separate templates. I might even do it if I had a single progress bar if it made sense to separate it out for ease of data handling.
Finally, inter-communications between Templates. This can be tricky at times, but the best, most efficient way to do this I have found is through Session variables. The pattern I typically use is to have my data for my template be returned by a Template .helper, which does something like this:
Template.controlBar.helpers({
progressData: function() {
if (!Session.equals('playId', null)) {
return Progress.findOne({_play_id: Session.get('playId')});
}
}
});
Because Helpers are reactive, and Sessions is reactive, the template is re-rendered anytime the 'playId' is altered in the Session. The corresponding Session variable can be set from anywhere in the client code. Again, this tends to work best when you narrow the scope of your templates to the individual components. It is important to note here that the Session object in Meteor is NOT the same as "sessions" in other languages like Java and such, which typically use cookies and a session token/id. Meteor sessions work considerably different, and do not survive page reloads or closing of browsers.