Vue components with partial reusability - javascript

I'm working on a large app with legacy code. I've run into this issue twice now and am realizing there must be a better solution than what I've done to solve it. The issue is as follows.
There are 3 separate pages where I need to add very similar Vue functionality. However, these pages have significantly different HTML/Blade templates. Therefore, it's like I have to pass in separate HTML/Blade templates to the component in addition to component props.
I can kind of accomplish this using Vue inline-templates, which takes care of the significantly different HTML/Blade template problem.
However, the remaining issue is that I have 3 .js Vue components, one for each page. This would be fine, except the Vue code in each file is very similar.
It's also possible that at some point I will need to add more unique Vue code to each component, and would like to keep that possibility open.
What I would like to do is find a way to reuse the Vue code that is very similar in each component.
I have tried thinking of a way to nest the same child component within each of these 3 separate components, but I don't see how that would be possible due to the differences in the HTML/Blade in each file.
Any suggestions would be most appreciated, as I feel like I'm duplicating too much Vue code!

Thanks to user thanksd for providing the solution in the comments above. Mixins indeed were the way to go for me. That way, instead of this:
Vue.component('first-component', {
template: // something unique
methods : {
functionNumber1: function () {
// do something
},
});
Vue.component('second-component', {
template: // something totally different
methods : {
functionNumber1: function () {
// do same something
},
}
});
I can essentially do this:
const myMixin = {
methods : {
functionNumber1: function () {
// do same something
},
}
Vue.component('first-component', {
template: // something unique
mixins: ['myMixin']
});
Vue.component('second-component', {
template: // something totally different
mixins: ['myMixin']
});

Related

Angular component not changing template

In the project I'm working on there some components structured like this:
;(function () {
'use strict'
angular
.module('a')
.component('comp', {
templateUrl: function (Config) {
return Config.path + '/a/a.template.html'
}
...
And every component had its own template file, but there were little to no difference beetween them so I created one template file to be used in all of them, but when I changed a.template.html to b.template.html only in some of the components the changes took place, and when I inspected the unchanged elements the HTML that was being used was from a.template.html. Why only some of the components being changed? (Also the components have the same hiarchy and are also really alike)
I figured it out, turns out that my browser was storing the HTML in the cache, so what worked was to just clear the browser cache.

Using Redux with vanilla JS how can I reach it without passing it to every component?

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 have some questions about Sapper/Svelte

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.

How to have all the static strings in one place

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.

MithrilJS: Routing a component inside top level component

I just started reading about Mithril. Fascinating..
Just one thing that puzzles me after first read.
How can I route one component inside another (top-level component)? I mean, how do I emulate angulars ng-view or embers outlet?
I understand that I can get m.route to attach components to any dom node.
But how can I render say top level component App, which generates m("#view") among other things, and then all other routable components go inside App's #view div? Is this possible? Otherwise I have to repeatedly include header and footer with every route transition to a subcomponent, right? Am I missing something?
Thank you.
Otherwise I have to repeatedly include header and footer with every route transition to a subcomponent, right? Am I missing something?
I don't think you're missing anything. Mithril has as little magic as possible, so it's hard to miss things. Yet it's still somehow more convenient than frameworks with magic.
I simply wrap my views in a template function. I'm a lazy guy, but even I don't mind doing this because it's flexible and not confusing.
http://codepen.io/farzher/pen/vOjjEB
function viewTemplate(content) {
return function() {return [
m('#header', [
'my site',
m('a', {config:m.route, href:'/'}, 'home'),
m('a', {config:m.route, href:'/1'}, 'page 1'),
m('a', {config:m.route, href:'/2'}, 'page 2'),
]),
m('hr'),
m("#view", content),
m('#footer', 'copyright my site'),
]}
}
component1 = {
view: viewTemplate([
m('h1', 'component 1 page')
])
}
component2 = {
view: viewTemplate([
m('h1', 'component 2 page')
])
}
m.route(document.body, '/', {
'/': {view: viewTemplate()},
'/1': component1,
'/2': component2,
})
I ended up going with on of the Leo's suggestions I found googling around.
I can only have "one-layer" wrap and no named outlets with this solution but it works and does the job for now.
At the end of the day, Angular has only one ng-view and people get by somehow.
So this is the outer component.
var Layout = {
controller(subcomp) {
this.own = {
slide: false
};
this.subctrl = new subcomp.controller();
this.subview = subcomp.view;
},
view(ctrl) {
return bubble(ctrl.own, ctrl.subview(ctrl.subctrl));
},
wrap(routes) {
var map = {};
Object.keys(routes).map((r) => {
map[r] = {
controller() {
return new Layout.controller(routes[r]);
},
view: Layout.view
};
});
return map;
}
};
This is the outer view where you insert your component.
function bubble(vm, subview) {
return m("main", [
m("#outlet",[ subview ])
]);
}
And then you route all your subcomponents inside the layout.
m.route.mode = "pathname";
m.route(document.body, "/articles/create", Layout.wrap({
"/articles/create": CreateArticle
}));
Hope this helps someone in the same situation.
I tried several solutions:
With m.component for route handler -
http://jsfiddle.net/0xwq00zm/1/
With internal method of the App component, that wraps the inner component. This is somewhat better, cause I'm able to pass aplication state to other components - http://jsfiddle.net/0xwq00zm/11/
With simple external function, that wraps the inner component with
other elements - http://jsfiddle.net/0xwq00zm/12/
More or less complex - with all of them I have the feeling that the apps redraws itself, and not only the inner component.
Simply select all the elements - Ctrl+A in the result pane in JsFiddle - and then navigate. It's virtual DOM and it shouldn't re-render everything, but with all solutions above - it happens.
(I tried also with context.retain = true; on some parts, but still after a few navigations I get to a point where nothing gets selected.)
========
Hope these variants help someone ... but also - I'll be happy to see solution of the total re-rendering.

Categories

Resources