js-data.io as Vue.js store - javascript

I'd like to use js-data 3.0.1 as store for my Vue.js 2.4.2 SPA. Everything works like a charm, but unfortunately I can't make the reactive data bindings work.
I've already tried vue-js-data, which seems to be broken.
The following example doesn't work. If I change the text fields, the text will not be updated. However if I replace the js-data record with a plain old JS object, it works like expected.
user.vue:
<template>
<div>
<input name="name" v-model="user.name" />
<br />
Name: {{user.name}}
</div>
</template>
<script>
export default {
data() {
return {
user: this.store.createRecord('user')
}
}
}
</script>
I'm thankful for any advice how to make the data binding (via v-model for example) work.

If you add this to the constructor of your User class, it should work for all root level properties. See here for a complete example: https://gist.github.com/calebroseland/2fa37abdb5560739b3b4b901382b0a90
// apply vue reactivity
for (let key in this) {
Vue.util.defineReactive(this, key, this[key])
}
// re-apply js-data schema
this._mapper().schema.apply(this)
The reason Vue and Js-Data reactivity don't work together out-of-the-box is that they both have separate implementations that use slightly different mechanisms. Here's an explanation: https://medium.com/p/525ffe12ad81#c925

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

InertiaJS How to use "Nested Layouts" for tabs?

I was hoping to get some additional information about the "Nested Layouts" in InertiaJS. The documentation is very scarce on it and I can barely find any sort of examples showing how it works, and the documentation on the site isn't very descriptive about how it works or what the code is doing. (https://inertiajs.com/pages#persistent-layouts)
Basically I want to achieve functionality similar to in this tweet here;
https://twitter.com/klaasgeldof/status/1181198792995037184
Hopefully someone can provide some extra information because I've been having a lot of trouble getting this working correctly.
There are many approaches to this and I'd say they are more related to what your frontend stack is. Since this question is tagged with vue.js I'll answer based on that.
Approach #1
The inertia part here is just creating a layout - which is basically just a vue component. That layout then contains the side and top navigation and a slot to fill the body content. You can then create a dashboard page component which utilize that layout by adding layout: myDashboardLayout in the export.
To get the same effect, you basically route to different views, which pass different slots to the layout and their respective data.
// Your DashboardLayout.vue
<template>
<div>
<my-sidebar :data="foo" />
<my-topbar :data="bar" />
<slot name="dashboardContent" />
</div>
</template>
// Your Dashboard/Index.vue
<template>
<main slot="dashboardContent" :data="myData" />
</template>
<script>
import DashboardLayout from './DashboardLayout'
export default {
// Using the shorthand
layout: DashboardLayout,
}
</script>
//web.php
Route::get('/dashboard', function(){
return Inertia::render('Dashboard/Index', [data]);
});
Route::get('/dashboard/foo', function(){
return Inertia::render('Dashboard/Index', [fooData]);
});
Route::get('/dashboard/bar', function(){
return Inertia::render('Dashboard/Index', [barData]);
});
You then either visit domain.com/dashboard/foo or domain.com/dashboard/bar
Approach #2
Having the same layout view, but passing a "tab-view" component. You'd not switch the routes at all and provide that one tab-view component with props to render the three different tabs.
Depending on the data, this approach could be slower as you fetch everything up front.
With #1 you fetch the data on demand and since it's an SPA, the user would't notice a difference.
Another benefit of #1 is, the user can actually actively visit that one specific tab and bookmark its URL for frequent access. You could do the same with anchors/hashes but you're using an SPA after all.
I could go on, but choose either option or perhaps that was enough to give you some inspiration.
Add a <slot /> inside tab
Now the trick is to put two Layouts on the nested page.
https://inertiajs.com/pages
<script>
import SiteLayout from './SiteLayout'
import NestedLayout from './NestedLayout'
export default {
// Using a render function
layout: (h, page) => {
return h(SiteLayout, [
h(NestedLayout, [page]),
])
},
// Using the shorthand
layout: [SiteLayout, NestedLayout],
props: {
user: Object,
},
}
</script>
layout: [SiteLayout, NestedLayout] this is where the trick is.

How to make a React.js inline portal for a jQuery Datepicker

I've watched,
http://youtu.be/z5e7kWSHWTg?t=15m17s
and read,
https://github.com/ryanflorence/react-training/blob/gh-pages/lessons/05-wrapping-dom-libs.md
https://github.com/ryanflorence/react-training/tree/gh-pages/code/Dialog
http://jsbin.com/dutuqimawo/edit?js,output
How to create a React Modal(which is append to `<body>`) with transitions?
and I get the concept of the Portal, that you're tricking React into ceasing its rendering for one piece of the DOM, then continuing the rendering afterward, so you can tinker with that piece of the DOM without confusing React by making its virtual DOM get out of sync.
My problem is that the examples all address a Dialog that is rendered at the end of the page, but appears inline when you're reviewing your code. It's a cool trick for using a jQuery modal, but I need a jQuery datepicker whose div actually remains where I put it. (As an aside, I'm also curious about GetDOMNode's presence in the examples when it's deprecated? I suppose you use FindDOMNode, although you call it slightly differently, plus the documentation says "In most cases, use of this escape hatch is discouraged because it pierces the component abstraction", which makes me a little gunshy to use it.)
To isolate the jQuery datepicker from React, I originally created one React component to handle everything above the datepicker, and another to handle everything below the datepicker, and used event listeners in each component to listen for updates. However, I prefer the design of a single parent component that passes everything down to its children; it seems like a cleaner design.
I redesigned it with a single parent and it seems to work, but I have no idea if my portal is really isolated from React's virtual DOM or not; it's my first crack at a portal so I'm really muddling through. (I am using React-Bootstrap for my navbar and it works great; I just couldn't find an equivalent to jQuery's datepicker and I like how it looks and operates, which is why I'm sticking with it.)
Here's my top-level component (I removed the props/componentDidMount/etc for clarity). The <CalendarControl /> is going to be the portal:
var ReactApp = React.createClass({
render: function() {
return (
<div>
<BootstrapNavbar division={this.state.division} dps={this.state.dps} sections={this.state.sections} />
<div className="container">
<br />
<br />
<br />
<div className="row">
<div className="col-md-4" id="calendarPortal">
<CalendarControl />
</div>
<div className="col-md-8">
<h3>{this.state.dp}</h3>
<h4>{this.state.dpStartDate} - {this.state.dpEndDate}</h4>
</div>
</div>
<TimebookTableRecords timebookRecords={this.state.timebookRecs} />
</div>
</div>
);
}
});
Here's the code for my CalendarControl portal. When the CalendarControl mounts, I'm creating a new div calendarControl as a child of calendarPortal. I then use jQuery to create the datepicker on the calendarControl div.
var CalendarControl = React.createClass({
render: function () {
return null;
},
componentDidMount() {
var portalLocation = document.getElementById("calendarPortal");
var newElement = document.createElement('div');
newElement.id = "calendarControl";
portalLocation.appendChild(newElement);
},
componentWillUnmount() {
var portalLocation = document.getElementById("calendarPortal");
document.body.removeChild(portalLocation);
},
});
Here's the jQuery code that creates a datepicker on the calendarControl div:
$("#calendarControl").datepicker({
numberOfMonths: monthDiff,
defaultDate: dpStartDate,
showButtonPanel: false,
beforeShowDay: formatCalendarDays, //formatter function
onSelect: dateClicked //handles click on calendar date
The final product seems to work fine, and doesn't generate any "the DOM was unexpectedly mutated" errors like when you manipulate part of the DOM that's under React's purview. I can update the state of the parent and see the changes propagate down nicely, and use jQuery to update the calendar.
However, I just don't know if this is the correct approach? That is to say, have I achieved a true portal here? I used the Google Chrome React Developer Tools add-in to inspect the component hierarchy, and it does look like from React's perspective there's a null in the CalendarControl div:
Thanks for bearing with me in this lengthy post. I have to say that so far I'm really loving the React approach to web development; it's so radically different that it took a number of readings and tinkering just to understand its concepts, but now it seems so much more elegant than the ways I've done it in the past.
From my understanding of portals, you are doing this mostly correct. But if it had any other children, you would have to reconnect with react after the jquery stuff, but I assume that is not the case here.
The reason you are seeing a "null" inside calendar control is because you return a null in your CalendarControl render function.
Why don't you just change your render function in calendarControl to:
render: function () {
return (
<div id="calendarControl"></div>
)
and do all your funky jQuery rendering inside componentDidMount function?

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