Passing data through nested components in Vue.js - javascript

I am writing a page using Vue.js and am attempting some custom components. I've started with a very simple one:
Vue.component('single-field', {
template: '<b>{{ key }}:</b> {{ value }}',
props: ['key', 'value']
});
This works fine on its own: it is intended to take two values and return them in a key: value format. Initially, when I was passing the value as an element from the Vue data object (specifically, pickup.dateTime it wasn't working. It's easier to show than explain:
<div id="app">
<single-field key="Date & Time" value="pickup.dateTime"></single-field>
</div>
I was able to fix this by binding the value (notice the colon by value):
<single-field key="Date & Time" :value="pickup.dateTime"></single-field>
I then wrote a second component which is designed to take an object and a title and return each element of the object as a set of <single-field> components, with a title above them. Here is what I wrote:
Vue.component('field-block', {
template: '<h2>{{title}}</h2>\
<div v-for="(p-key, p-value) in parent">\
<single-field key="p-key" value="p-value"></single-field>\
</div>',
props: ['parent', 'title']
});
Now, I think there must be something wrong with the way I am binding (or not binding?) which is causing the data to be displayed in a strange way. Here is my HTML:
<div id="app">
<single-field key="DateTime" :value="pickup.dateTime"></single-field>
<single-field key="Name" :value="pickup.name"></single-field>
<field-block title="Address" :parent="pickup.address"></field-block>
</div>
The field pickup.address is an object, so I was hoping that the component would iterate through the elements and return them as single fields, but what it's currently doing is just returning the variable names, like p-key: p-value. I tried doing a bind on both of them in the component definition (on both because they are now both variables being passed whereas previously I was using a static string for the key field):
<div v-for="(p-key, p-value) in parent">
<single-field :key="p-key" :value="p-value"></single-field>
</div>
But what this returns is NaN: NaN, and I can't figure out why it would do that. I don't fully understand the bind operation, but I expect that it only binds to data in the data option when defining the view, which is not where p-key and p-value are defined. How would I get the variables to 'carry through' from the outer component to the inner ones? Thanks.

Okay this one was tricky.
Several problems with your code:
Your components should have only one root element. So pack your components into a <div>.
Then, - is not a valid character to put into a javascript variable name. So you can't write (p-key, p-value) in parent, write something else, like (pKey, pValue) in parent.
On the other hand, html properties are not case-sensitive, so you should actually write key-name (for example) instead of keyName, but only for the property names. This limitation does apply only in your html files (not in your string templates).
Finally, here is a working js-fiddle.
https://jsfiddle.net/6juwLd3b/
Additionally, I would advise you that you look at your console while developing. Many error where displayed that could have lead you to a working version.

Related

Rendering React components from an HTML string

I'm getting an HTML string from an AJAX request, that looks something like that:
<div> <SpecialComponent/> <SecondSpecialComponent/></div>
What i need, is to be able use this string in a React component, as it were valid JSX.
I tried using the dangerouslySetInnerHTML as instructed in this discussion:
how-to-parse-html-to-react-component
I've also tried React libraries like react-html-parse and html-react-parser. No success
When i was working with AngularJS(1), i was using some small directive that would take care of that situation. I need to achieve the same with React.
Any ideas?
EDIT: if anybody is interested, i found a library that takes care of the issue:
https://www.npmjs.com/package/react-jsx-parser
You're not supposed to be doing things this way. Although React components look like html tags when using JSX, they are actually either classes or functions. What you're trying to do is equivalent to trying to parse:
"<div>document.write('foo')</div>"
You're literally mixing a javascript function name in string form with html markup in the same string. You will have to parse the string to separate the React component from the surrounding html markup, map the React components in string form to their actual React counterparts, and use ReactDOM.render to add the component to the page.
let mapping = [
{"name":"<SpecialComponent/><SecondSpecialComponent/>","component":<SpecialComponent/><SecondSpecialComponent/>},
{"name":"<someOtherComponent/><someOtherComponent/>","component":<someOtherComponent/><someOtherComponent/>}
];
let markup = "<div><SpecialComponent/><SecondSpecialComponent/></div>";
let targetComponent;
mapping.forEach((obj) => {if(markup.indexOf(obj.name)>-1){
targetComponent=obj.component;//acquired a reference to the actual component
markup.replace(obj.name,"");//remove the component markup from the string
}
});
/*place the empty div at the target location within the page
give it an "id" attribute either using element.setAttribute("id","label") or
by just modifying the string to add id="label" before the div is placed in
the DOM ie, <div id='label'></div>*/
//finally add the component using the id assigned to it
ReactDOM.render(targetComponent,document.getElementById("label"));
Never tried this before. I wonder if this will actually work :)
my suggestion is you do something like the following.
first set a state variable to equal the HTML you want to return
this.setState({html: response.data.html})
then in your return you can do the following
return (
{this.state.html}
);

two way data binding in angular 2

I am quiet recent to angular 2 and was bumping my head around two way data binding. Here is the confusion:
<input (input)="username = $event.target.value" id="testing">
<p>{{username}}</p>
This serves the purpose well. It already seems to be two way data binded. I can access username inside the component and the value property of the input element is updated as well. Why do i need [ ] at all then? What is the two way binding here? what goes inside the component and what comes out of the component.
Disclaimer: I know it's a very naive and seemingly stupid question :|
As i understand write like you did:
(input)="username = $event.target.value"
is the same as:
[(input)]="username"
So they're both 2 way data binding
if you want only one way:
(input)="username"
https://angular.io/docs/ts/latest/guide/template-syntax.html
Angular 2 data binding is explained here.
When you write in the input you execute: "username = $event.target.value", wich assigns to the variable user the value you enter.
That means that the following html does the same:
[(ngModel)]="username"
[username]="username" (input)="updateUserName()"
(input)="username = $event.target.value" and {{userName}}

First javascript project with Durandal. Trying to get data from 3rd party API

So i'm working on a project where players of a game will be able to compare their performance with those of their peers at the same skill level. I can get a prototype of the code to work outside of a durandal structure, but when I try to follow along with other examples while supplying my own sources for the data, I just can't get it all together.
Here is my code:
define(function (require) {
var http = require('plugins/http'),
ko = require('knockout');
var url = 'https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/',
key = '?api_key=#################################';
return {
name: ko.observable,
getSummoner: function() {
var that = this;
if (this.name.length > 0) {
return;
}
return http.jsonp(url + name + key, 'jsoncallback').then(function(response){
that.name(response.items);
});
}
};
});
Replace the #'s with my personal API key that the host recommends I don't share. I'll supply one if necessary and just change it later.
I have 2 specific questions here:
I got the function structure from a tutorial. I don't know why I need to check for length with the IF statement. What is that returning exactly?
This api call returns a JSON object with a nested object inside. What I want, is to display the keys and values from the nested object as li's on the view. Right now I can't even get it to tell me if its actually grabbing the object in the first place.
Here is my HTML:
<section>
<h2>Hello! What user would you like to investigate?</h2>
<form class="form-inline">
<fieldset>
<label>Name</label>
<input type="text" data-bind="value: name, valueUpdate: 'afterkeydown'"/> <!--Text input box-->
<button type="submit" class="btn" data-bind="click: getSummoner, enable: name">Click Me</button><!--This button has both a class and an ID,
Css is linked from index.html-->
<ul data-bind="foreach: name">
<li data-bind="text:$data"></li>
</ul>
</fieldset>
</form>
</section>
What I expect to see is just 1 bullet item that either says object or whatever name is fed into the input box. What I get is nothing.
The returned object (with my username attached) looks like this:
{"ryebrush":{"id":25500750,"name":"RyeBrush","profileIconId":551,"summonerLevel":30,"revisionDate":1426533699000}}
I want to be able to access the ryebrush.id, ryebrush.profileIconId, and so on and so forth. Help?
EDIT: Also, this comes pre-loaded in the input box:
function (b){function c(){if(0<arguments.length)return c.Ka(d,arguments[0])&&(c.P(),d=arguments[0],c.O()),this;a.k.zb(c);return d}var d=b;a.N.call(c);a.a.sa(c,a.m.fn);c.o=function(){return d};c.O=function(){c.notifySubscribers(d)};c.P=function(){c.notifySubscribers(d,"beforeChange")};a.s(c,"peek",c.o);a.s(c,"valueHasMutated",c.O);a.s(c,"valueWillMutate",c.P);return c}
Uhhhhhh.....what?
You sort of asked two questions, so I'm sort of going to give you two and a half answers.
Before we address your first question, you are using the knockout observable function incorrectly, and that is going to cause you lots of headaches. Let's fix that. The following two lines of code will both work similarly. When you call the observable function, you create a new instance of an observable. If you call it with no arguments, the value of the observable is undefined. Since we know you want a string here, it may be preferable to initialize it to the empty string, as in the second example.
name: ko.observable(),
or
name: ko.observable(''),
Then, we can set or retrieve the value of the observable by calling it as a function:
that.name('value');
and
that.name() == 'value';
I got the function structure from a tutorial. I don't know why I need
to check for length with the IF statement. What is that returning
exactly?
After the if statement, you have the following line of code:
http.jsonp(url + name + key, 'jsoncallback')
If name is undefined or empty, you will attempt to make this call to one of two urls respectively:
https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/undefined/?api_key=#
or
https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name//?api_key=#
We know that both should return an error (probably a 400), so there's no point in making those calls. The if statement, when applied to a string, is true when the string is initialized and empty. Note, this will throw an error if the string is undefined, and that's no good.
However, the syntax is also wrong. Technically, the value of that.name is a function and, when treated as a string, will evaluate as
function (b){function c(){if(0<arguments.length)return c.Ka(d,arguments[0])&&(c.P(),d=arguments[0],c.O()),this;a.k.zb(c);return d}var d=b;a.N.call(c);a.a.sa(c,a.m.fn);c.o=function(){return d};c.O=function(){c.notifySubscribers(d)};c.P=function(){c.notifySubscribers(d,"beforeChange")};a.s(c,"peek",c.o);a.s(c,"valueHasMutated",c.O);a.s(c,"valueWillMutate",c.P);return c}
Recall from above, a much better thing to write in the if statement is
if (!that.name())
We call the function to get the value. Both '' and undefined are falsey in javascript, and so the if statement will catch both cases and exit, which is what we want. Note it will also exit if the value of name is 0, or any other falsey javascript value.
This api call returns a JSON object with a nested object inside. What
I want, is to display the keys and values from the nested object as
li's on the view. Right now I can't even get it to tell me if its
actually grabbing the object in the first place.
There are a number of things wrong with your view.
<ul data-bind="foreach: name">
This will iterate through the value of that.name. If that.name is 'ryebrush', this will (I believe), iterate through each letter. That's no good. If your goal is to have a list of summoners in that.name, you will want to swap ko.observable with ko.observableArray. You may also want to change the name that.name to that.names to avoid confusion.
<li data-bind="text:$data"></li>
This is right if your array is filled with strings. foreach will iterate through each item in the array, and $data is each item. However, if the item is in fact an object, you can reference properties on the item. For example, if each item in your array is
{"id":25500750,"name":"RyeBrush","profileIconId":551,"summonerLevel":30,"revisionDate":1426533699000}
then, since name is a property on the object, you can reference name in your view
<li data-bind="text:name"></li>
Finally, you're not actually getting the data in the right place after your http call. If the response to your http call is
{"ryebrush":{"id":25500750,"name":"RyeBrush","profileIconId":551,"summonerLevel":30,"revisionDate":1426533699000}}
Then you would want to write
return http.jsonp(url + name + key, 'jsoncallback')
.then(function(response){
that.name(response["ryebrush"]);
});
If the response is instead an array of items
[{"ryebrush":{"id":25500750,"name":"RyeBrush","profileIconId":551,"summonerLevel":30,"revisionDate":1426533699000}}]
Then you would want to write
return http.jsonp(url + name + key, 'jsoncallback')
.then(function(response){
that.name(response[0]["ryebrush"]);
});
Conclusion
Unfortunately, this isn't a great question. I can't tell exactly what's going on with the API you're using, so I can't tell you exactly what you should write. To accomplish what you're trying to do, you'll need to spend a little bit of time reading up on javascript, knockout, and durandal. Here are some good resources for each:
javascript
knockout
durandal
However, I see you're a new user. I want to encourage you not to get discouraged. It'll take a bit of time to to learn the ropes, but it's worth it. Don't give up. I hope this helps!

Multi-Dimensional Arrays in Ractive.js

I've been playing around with the fantastic Ractive.js library (not to be confused with Facebook's Reactive.js). I worked out that you can render a two-dimensional using the following template code:
<div class="container">
{{ #frameContainer:i }}
<div class="row">
{{ #frameContainer[i] }}
<div on-click="cell-click" class="cell {{ . ? 'on' : 'off' }}"></div>
{{ /frameContainer[] }}
</div>
{{ /frameContainer }}
</div>
This works exactly as I'd expect and the inner cell-click event returns the correct keypath (e.g. frameContainer.2.4).
I then wanted to turn this into a three-dimensional array (to add a "time" axis). However, the following did not work:
<div class="container">
{{ #frameContainer[time]:i }}
<div class="row">
{{ #frameContainer[time][i] }}
<div on-click="cell-click" class="cell {{ . ? 'on' : 'off' }}"></div>
{{ /frameContainer[][] }}
</div>
{{ /frameContainer }}
</div>
Where time represents the current time value (it will only ever show one "time" at a time - seems obvious when you say it like that...).
This... sort of works. It displays the grid as it should, but the returned keypath for the cell-click event is no longer correct, returning something like ${frameContainer-time-8-}.2 - which has lost the time value (and has gone a bit weird).
Obviously I could just do it by having a currentFrame value which I render from and switch around with ractive.set(), but that seems less elegant. Is there a way to do it purely within the template? And, if not, what's the most efficient way of doing it otherwise?
Thanks!
This is quite a tricky one. Basically, the ${frameContainer-time-8-} keypath is how Ractive uniquely identifies an expression, and ${frameContainer-time-8-}.2 means 'the third member of whatever that expression evaluates to'.
What's happening here is this: When Ractive's parser sees the {{ #frameContainer[time][i] }} section, it parses frameContainer[time][i] as a JavaScript expression and turns it into the following (you can try this yourself - Ractive.parse('{{#frameContainer[time][i]}}')):
{
r: ['i','time','frameContainer'],
s: '${2}[${1}][${0}]'
}
When the template is rendered, Ractive creates an evaluator for that expression, which has a function generated from the string (the s property), and which watches the time and frameContainer values (it doesn't need to watch i because it can't change). When either or both of those values change, the function is executed with them as arguments. If it returns a changed value, Ractive needs to update the view.
Since there's a good mechanism for propagating viewmodel changes to the view - keypaths - that's what the evaluator uses. In order to do so, it needs to create a unique keypath, hence ${frameContainer-time-8-} (it can't contain dots or square brackets, because Ractive would try to split on those).
So here's the thing: expression keypaths are one-way. You can't do ractive.set('${frameContainer-time-8-}.2', 'true') like you can with regular keypaths, because Ractive can't figure out what underlying property that corresponds to (or if there even is one - it might be a derivative value for all it knows).
Solution 1
The simplest workaround would probably be to do something like this:
<div on-click="cell-click:{{time}},{{i}}" class="cell {{ . ? 'on' : 'off' }}"></div>
You can then use the time and i values in your cell-click handler (they will be the second and third arguments, after event).
I've done a simplified demo here (assuming I understood you correctly): http://jsfiddle.net/rich_harris/LYEGX/
Solution 2
The other way would be to do away with keypaths altogether and use an adaptor. I won't go into all the details here as it may not be the answer you're looking for, but there's some documentation and an example (some of the links are currently out of date, sorry...). In short, this method assumes you're able to use non-POJOs in your app.

Pass data to dynamically created template with meteor

Following up on this queston/answer (second answer) Dynamically loading templates in Meteor.js
I have set up a helper in the current displayed template and i am returning the template dynamically. This works great.
Template.template1.helpers({
dynamicView: function(){
return Template['dynamic_template']();
}
})
This ends up showing my html in template 1:
Questions:
How do i pass data to this Template as it's being created, can i do something like this:
Template['dynamic_template'](data1, jsonData2);
I want the dynamicView helper in template1 to also be dynamic in the sense that it can have a different template based on some other criteria. Can i use a Session here and change the dynamicView return statement to something like this:
dynamicView: function(){
return Session.get('dynamicTemplate');
}
and somewhere else Session.set('dynamicTemplate', Template['dynamic_template']()); This works, but is it recommended. It's just a string but i am concerned about performance problems and the size of my template being passed reactively
OK I guess ill have to split my answer up:
With 1.
The templates are compiled handlebars templates so you just need to provide a context to them so the handlebars data can be filled up:
data = {name1:value1, name2:value2}
return Template['dynamic_template'](data);
So that {{name1}} and {{name2}} get filled up with value1 and value2 respectively.
With 2.
Yes that should work, you can pass off any data that will give off HTML as a result. For a very detailed videocast have a look at the EventedMind screencast on template functions: http://www.eventedmind.com/posts/meteor-rendering-template-functions
The template system's use case might not be this exactly. You might want to use Meteor.render instead, but it depends on what you want to do.
Despite the Session name, its just a reactive javascript variable so it should be fine with larger strings too to the same that would be used in an ordinary javascript variable

Categories