Vue.js workflow from Vue.js 1 to 2 - javascript

I have followed the Vue.js lessons from laracasts when Vue.js 1 came out and I used to do something like this:
import Comments from './components/Comments.vue';
import Upload from './components/Upload.vue';
new Vue({
el: 'body',
components: {
Comments,
Upload,
Preview,
Algolia,
},
etc,
});
This allowed me to kind of 'sprinkle' components all over my application. I can no longer bind to the body though because Vue replaces the content and also throws an error message saying you shouldn't bind to the body or html.
I followed a couple of lessons for Vue.js 2 but how can I replicate this workflow in the Vue.js 2 manner? I loved just binding to the body and having the option to place a component here and there with the custom tags.

We use the same "sprinkling" approach and all we did was change it from 'body' to '#app'.
We also added a wrapping element inside that had this id to basically replicate body. (https://github.com/onespacemedia/project-template/blob/develop/%7B%7Bcookiecutter.repo_name%7D%7D/%7B%7Bcookiecutter.package_name%7D%7D/templates/base.html#L62)
<body>
<div id="app">
...
</div>
</body>
We use Jinja2 for our templating language and have found when a variable that doesn't resolve in Jinja2 it tanks Vue as well as i think Vue tries to use it.
I believe it takes everything inside #app after initial render and converts it to virtual dom. This doesn't effect anything from what i've seen though so you can happily just add the wrapping class inside body and use it the same as Vue 1

Related

Change in HTML attribute of web component not reflected in Vue component

I'm facing the below problem.
I have a pure web component:
<my-web-comp options='["op1", "op2"]' active-option="op2"></my-web-comp>
This renders as two tabs with the second one selected by default. When you click on the other, the active-option HTML attribute changes to op1 and you can actually see that the property is changing in the DOM if you open the DevTools.
However, I cannot detect the change in the Vue component where I am using the web component. I have:
<template>
<div>
<my-web-comp :options="options" :active-option="activeOption"></my-web-comp>
</div>
</template>
<script>
export default {
name: 'MyVueComponent',
data() {
return {
options: '["op1", "op2"]',
activeOption: "op2"
}
},
computed: {
testVar() {
console.log("activeOption", this.activeOption) <--------- THIS LINE
},
}
}
</script>
The marked line only gets fired on the first load of the Vue component (printing "op2"). After that, testVar never gets modified again, doesn't mind if I click on the other tab and I don't see nothing in the console.
What can I be missing? I think it can be something related with Vue reactivity system, but can't wonder what.
This happens because your web-component mutates copy not a reference of this variable (copy created by your web component is also not reactive). There are two ways to change this:
You can modify your web component to use getters and setters to change value of this variable
You can use MutationObserver. To detect changes in your web-component. This approach will not require changes in this web-component
If you choose approach with MutationObserver then create this observer in vue mounted life-cycle-hook

I'm trying to render a react component on DOM node, defined in a pug file

Background Information
The basic structure of my initial application was
main.pug
... code elements ...
<div class = "panel">
... code elements ...
and a starter javascript file that is loaded in the header, which contains this block of code
starter.js
document.addEventListener('DOMContentLoaded', function() {
... logic to determine html page to show in the panel div ...
... it determines the name and saves it in the variable 'htmlPageToShow' ...
$(".panel").load(htmlPageToShow);
});
this bit of code in starter.js is how content is displayed in the panel div
it determines the file name and then loads it into the dom element
Problem
I decided that i wanted to have React handle this part, my conclusion was to render a React component called into the panel div, in hopes of improving this bit of the code without having to change the entire code base but two days in, and I cannot get a react component, or html element of any kind, to render in the div, similar to how html pages were loaded
My attempts
I left the main.pug file alone and in starter.js, did this:
starter.js
import react from 'react';
import ReactDom from 'react-dom';
import PanelHandler from './PanelHandler';
ReactDom.render(
<PanelHandler/>,
document.getElementById('panel'));
I've tried multiple versions of this, such as replacing the get element id bit with $(".panel") I've tried using getElementByClassName, QuerySelector, I've tried using window.document
I've also tried putting the whole thing into a script tag and appending it to the end of main.pug
At some point, I tried creating a new html page, with the code above in a script tag, and then loading that into panel, with the previous logic, all to no avail
I want to know if I'm missing something, can pug just not be used with React? am I missing a dependency? any help at all would be appreciated!

Vue.js detach styles from template

I use a template with a <style> block which must be near its div for CMS reasons.
When I run Vue.js, it seems to remove the style block, saying...
- Templates should only be responsible for mapping the state to the UI.
Avoid placing tags with side-effects in your templates, such as <style>,
as they will not be parsed.
What can I do?
var app = new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js"></script>
<div id="app">
<style>
#div_123 {
background: http://placehold.it/850x150;
}
#media screen and (max-width: 640px) {
#div_123 {
background: http://placehold.it/350x150;
}
}
</style>
<div id="div_123">
Test
</div>
</div>
The Issue
In Vue 2 the root instance is treated more like a component than it was in Vue 1.
This means when you bind the Vue instance to #app it digests everything in #app as a vue template. This means tags are invalid and they'll be removed from the template. This is just the way things work in Vue 2.
Recreation
I recreated the issue in a codepen here
https://codepen.io/Fusty/pen/gqXavm?editors=1010
The <style> tag nested within the tag Vue is bound to. It should style the background red and the text color green. However, we see only a flash of this (depending on how fast your browser fires up Vue) and eventually vue removes these style tags as it digest #app as a template and then updates the DOM with what it thinks should be there (without <style> tags).
Better Recreation
Thanks to user #joestrouth1#6053 on the Vue-Land discord, we also have this fork of my recreation of the issue.
https://codepen.io/joestrouth1/pen/WPXrbg?editors=1011
Check out the console. It reads . . .
"[Vue warn]: Error compiling template:
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as <style>, as they will not be parsed.
1 | <div>
2 | <style>
| ^^^^^^^
... etc ...
Complaining about the style tags in a template.
This zeroes in on the actual issue. It is good to note this doesn't occur in Vue 1. Probably because it treats the root instance more uniquely than components, but I am not 100% sure on this topic.
Solution (Hack, not best practice or especially recommended)
The <style> tags are still in the DOM during the created lifecycle hook for the Vue instance and they are removed by the time the mountedlifecycle hook fires. Let's just query for all of the style tags within the #app element, save them, and then append them back to the #app element after Vue has digested the template.
Adding the following to your root Vue instance will take any <style> tags within whatever element your Vue instance is bound to (via el: 'someSelector') and append them (possibly relocating them) to the element your Vue instance is bound to.
created: function() {
this.styleTagNodeList = document.querySelector(this.$options.el).querySelectorAll('style');
},
mounted: function() {
for(var i = 0; i < this.styleTagNodeList.length; ++i)
this.$el.appendChild(this.styleTagNodeList[i]);
}
NOTE: This is definitely a hack which likely has unintended consequences I have not run into yet and cannot specifically disclaim. USE AT YOUR OWN RISK.
This works for my specific situation where I allow the users to store a string of CSS and then I need to render it on specific pages - ei: preview page.
The context here is css is saved as string in database, fetched and rendered within an Vue component.
# html
<html>
<head>
<style id="app_style"></style>
</head>
<body>
<div id="app"></div>
</body>
</html>
# app.vue
data() {
return {
dynamic_css: ''
}
},
created() {
// fetch css from database, set as `this.dynamic_css`
},
watch {
dynamic_css: function(newValue) {
document.getElementById('app_style').innerHTML = newValue
}
}

Pass reference of a component to another one in ReactJS

Before anyone press eagerly the close button, I already have looked the following question: ReactJS Two components communicating. My problem is exactly the third scenario developped in the current accepted answer.
I am using ReactJS to build something with two components. For HTML reasons (and presentation), i want my two components to be at two different places of the page.
For the moment, I have the following pattern, corresponding to scenario #2:
FooForm = React.createClass({
...
});
FooList = React.createClass({
...
});
FooManager = React.createClass({
...
render: function () {
return (
<div>
<FooForm ref="form" manager={this} />
<FooList ref="list" />
</div>
);
}
});
React.render(
<FooManager someProp={value} />,
document.getElementById('foo')
);
This gives something like:
<div id="foo">
<form>Form generated with the render of FooForm</form>
<ul>List generated with the render of FooList</ul>
</div>
However, i would like to have something like this:
<div id="fooform">
<form>Form generated with the render of FooForm</form>
</div>
<!-- Some HTML + other controls. Whatever I want in fact -->
<div>...</div>
<div id="foolist">
<ul>List generated with the render of FooList</ul>
</div>
The problem here is: how can I keep a reference in each component? Or at least the link Form -> List?
I tried to create the FooList before and pass the reference to the current manager, but I get the following warning/error:
Error: Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's `render` method). Try rendering this component inside of a new top-level component which will hold the ref.
The documentation says you can attach events to link two components which do not have a parent-child relation. But I don't see how. Can someone give me some pointers?
The Less Simple Communication lesson from react-training has a good example of how you can move actions & state sideways to avoid having to create an explicit link between related components.
You don't need to jump into a full Flux implementation to get the benefit of this approach, but it's a good example to lead you up to Flux, should you eventually need it or something like it.
Note that this requires you to model the relationship between the components based on changing state rather than explicitly passing a reference to a component instance (as you're doing above) or a callback bound to the component managing the state.
This would be the perfect use-case for a Flux type architecture.
What you want is someone FooManager to be able to trigger state changes in both components. Or, in fact, having the different components trigger, through Actions, state changes in each other.
The Flux Todo-App Tutorial illustrates your use-case perfectly!
After this, then you'd have the choices of using Facebooks implementation of Flux or the other gazillion ones.
My personal favorite is Reflux

ARIA attributes in ember core form components

I'd like to use ARIA attributes in Ember core form components, such input and textarea fields.
I noticed that using an aria attribute within the component in my template, it doesn't work at all
{{input aria-label="Your name"}}
{{textarea aria-label="Your address"}}
So I decided to reopen the core components in an initializer to add this attribute to the components
export default {
name: 'reopenTextAreaComponent',
initialize: function () {
Ember.TextArea.reopen({
attributeBindings: ['aria-label']
});
}
};
Since I did that, the performance of my application is pretty bad. The integration tests take much more time than before.
I tried not to use their components and simply a HTML tag:
<textarea {{bind-attr aria-label="Your address"}}>{{value}}</textarea>
But this doesn't compile with handlebars! It returns an error because of the {{value}} within the textarea tag.
What is the solution to avoid reopening? Should I create my own component?
Thanks
From Ember 2.8+, the simplest way of doing this is to install ember-component-attributes, like this:
ember install ember-component-attributes
Then you can add aria-label, other ARIA attributes and whatever other attributes you might require as follows:
{{input (html-attributes aria-label="Your name")}}
{{textarea (html-attributes aria-label="Your address")}}
What is the solution to avoid reopening? Should I create my own component?
Those are your two options.
I think you have the correct approach regarding the reopening of Ember.TextArea, because you want to have aria-label available on all instances.
I use a similar initializer - with one difference being that I reopen Ember.TextSupport so that both Ember.TextField and Ember.TextArea will have the aria-label binding.
// initializers/input.js
import Ember from 'ember';
export function initialize(/* application */) {
Ember.TextSupport.reopen({
attributeBindings: ['aria-label']
});
}
You could try creating your own component(s) to see if there is any difference in performance, but I think reopening is the right approach in this case.

Categories

Resources