I am trying to set up an example in which a series of news items will be passed in using ajax in a json format. At the moment I am just using a function to simulate returned data.
Here is the jsfiddle: http://jsfiddle.net/c8b4naL5/
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
<span data-bind="foreach: { data: newsItems, as: 'item' }" >
<!-- <span data-bind="foreach: { data: items, as: 'item' }"> -->
<div class="news-item">
<span data-bind='text:item.title'></span>
</div>
</span>
<script type="text/javascript">
function NewsItemsCall(){
return {
newsItemsFromCall: [
{title:'First Title From call'},
{title:'Second Title From call'}
]
}
}
function NewsItem(newsItemsCall){
var map = ko.mapping.fromJS(newsItemsCall);
return map;
}
var viewModel = {
newsItems:ko.observableArray([new NewsItem(new NewsItemsCall())])
}
ko.applyBindings(viewModel);
</script>
The ko.toJSON displays the following:
{
"newsItems": [
{
"newsItemsFromCall": [
{
"title": "First Title From call"
},
{
"title": "Second Title From call"
}
],
"__ko_mapping__": {
"ignore": [],
"include": [
"_destroy"
],
"copy": [],
"observe": [],
"mappedProperties": {
"newsItemsFromCall[0].title": true,
"newsItemsFromCall[1].title": true,
"newsItemsFromCall": true
},
"copiedProperties": {}
}
}
]
}
At this point I am just trying to get it to work to display the data in the template. Any insights would be appreciated. Thanks in advance.
Well the modification required could be approached from either the data side or the client side. At face value, your view isn't matched up to the data due to newsItems containing an array of newItemsFromCall. If the data is in the correct format, then just add another foreach binding.
Modifying the data
NewsItemsCall could return an array instead of an object
be aware of the return of the mapping call depending on how you will be using that value elsewhere
Modifying the UI
<span data-bind="foreach: { data: newsItems, as: 'item' }" >
<div data-bind='foreach: item.newsItemsFromCall'>
<span data-bind='text: title'></span>
</div>
</span>
Modified fiddle with changes to the data structure. I also included an alternate approach that maps the fromJS call directly as a viewmodel.
Example of mocking json calls in a fiddle.
Related
I'm trying create a follow button on list items in Vue. My strategy is to grab the value of a particular list item property and store it in the data object. Then use this value in a method to add it to an array in my database.
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock">+follow</button>
</div>
I'm not sure how to get the value of result.symbol "into" the button element to set the value symbol in the data object below.
<script>
export default {
data() {
return {
results: [ // this is populated by an api call
{
currency: "USD"
exchangeShortName: "NYSE"
name: "International Game Technology PLC"
stockExchange: "NYSE"
symbol: "IGT"
},
{...},
...
],
symbol: "",
};
},
followStock() {
// add this.symbol to database array
},
},
};
</script>
I'm guessing there might be an easier strategy I'm overlooking as I'm still new to Vue, so any other solution that essentially allows me to fire off the value of result.symbol from any rendered result to my database would be awesome.
You can just pass the result as a parameter to your method.
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result)">+follow</button>
</div>
And in your method:
methods: {
followStock(result) {
// do something with result
console.log({result});
let symbol = result.symbol;
},
}
P.S I didn't see you put your followStock() inside a methods object, but I did so in the example. https://v2.vuejs.org/v2/api/#methods
Write directly as a function call.
The vue compiler will turn followStock(result.symbol) into function(event) {followStock(result.symbol)}.
new Vue({
el: '#app',
data() {
return {
results: [
{
name: "International Game Technology PLC",
symbol: "IGT"
},
{
name: "A name",
symbol: "A symbol"
}
]
};
},
methods: {
followStock(symbol) {
console.log(symbol)
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result.symbol)">+follow</button>
</div>
</div>
As Nazaire mentioned you can access the results anywhere inside the child elements when using v-for.
(it works like a normal for-loop)
It's not only limited to the corresponding element (the element in which you do v-for)
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result.symbol)">+follow</button>
</div>
followStock(symbol){
// you can now add symbol to db
}
I have the following JSON object which maintain the sequence in it.
var sample={
"sample": [
{
"example": [
{
"sequence": 1,
},
{
"sequence":2
},
{
"sequence":3
}
]
},
{
"example": [
{
"sequence": 1,
}
]
}
]
};
$.templates("testingTemplate", "#testingSection");
var html=$.link.testingTemplate("#htmlHolder", sample);
$("#insert").click(function(){
var childIndexVal=parseInt($("#childIndex").val());
var x= {
"sequence": childIndexVal+1,
"xxx":"yyy"
};
var parentIndexVal=parseInt($("#parentIndex").val());
$.observable(sample.sample[parentIndexVal].example).insert(childIndexVal,x);
console.log(sample);
});
.parentHolder
{
border:1px solid red;
padding:5px;
margin:5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsviews/0.9.80/jsviews.js"></script>
<script id="testingSection" type="text/x-jsrender">
{{if sample && sample.length}}
{^{for sample }}
<div class="parentHolder">
{^{for example }}
<div><span data-link="#index+1" ></span>
<input type="text" value="{{:sequence}}" id="sequence" data-link="#index+1"></div>
{{/for}}
</div>
{{/for}}
{{/if}}
</script>
<div id="htmlHolder">
</div>
<div class="messages">
</div>
<span>Parent Index:</span><input type="text" id="parentIndex"/>
<span>Child Index:</span><input type="text" id="childIndex"/>
<button id="insert" >Insert</button>
so whenever I insert the object it should update the squence accodring to the index.
In the given example, if I am inserting an object into the 1st index of example in sample[0].(consider example array index starts from zero).
So when I am inserting one object into first parameter, the sequence in the remaining object should updated according to the index.
How can I achieve it.
give parent Index as 0 and child index as 1.
expected output,
Note: extra "xxx":"yyy" for differntiation purpose.
var sample={
"sample": [
{
"example": [
{
"sequence": 1,
},
{
"sequence": 2,
"xxx":"yyy"
}
{
"sequence":3
},
{
"sequence":4
}
]
},
{
"example": [
{
"sequence": 1,
}
]
}
]
};
Update: : tried with linkTo also.
<input type="text" data-link="linkTo=sequence #index+1" ></div>
Still not getting the expected output.
Thanks in advance.
Neither JsRender nor JsViews will modify the JSON data that they are rendering. This is by design: there are no side-effects on the data...
On the other hand, you can write code to create side-effects on the data, or you can use two-way binding so a user can modify data values. But two-way binding will only change the targetted data value, not other values elsewhere, and will do so only when the user triggers a change event on that <input> for example.
In your case you want any observable changes to an examples array to trigger changes to all examples in the array such as to ensure the example.sequence value is always equal to the index+1 for each example. In that case you have to write code to do that.
One way you can achieve that is to add the following code using observeAll:
$.observable(sample).observeAll(function(ev, eventArgs) {
if (ev.type==="arrayChange" && ev.data.observeAll.path().slice(-7)==="example") {
$.each(ev.currentTarget, function(i, item) {
$.observable(item).setProperty("sequence", i+1);
})
}
});
That will ensure that the sequence value stays in sync not only for insert, but also when items are removed or for changes in the the order of items, etc.
I'm having a JSON Collection, I wish to dispay Email ID which is marked as IsPreffered = TRUE using AngularJS HTML without .
user.Name
My JSON Collection :
{ "user" : [
{
"Name" : "B. Balamanigandan",
"Email": [
{
"Id": "bala#gmail.com",
"IsPreffered": true
},
{
"Id": "mani#gmail.com",
"IsPreffered": false
}
]}
]};
HTML Source Code:
<div>
<h3>Employee Details:</h3>
<span> {{collection.user.Name}} </span>
<span> {{collection.user.Email.Id}} </span>
</div>
The Collection user contains only one document. So, I didn't use ng-repeat. user is a Collection not a Document. One more check I need. If more than one email has `IsPreferred == true', then I have the take the last one. Kindly assist me.
Kindly filter the condition using HTML Level not in JavaScript Level. Kindly assist me.
instead of this:
<div>
<h3>Employee Details:</h3>
<span> {{collection.user.Name}} </span>
<span> {{collection.user.Email.Id}} </span>
</div>
you will have to do this:
<div>
<h3>Employee Details:</h3>
<span> {{collection.user[0].Name}} </span>
<span ng-repeat="email in collection.user[0].email| filter: {IsPreffered : 'true'}"> {{email.Id}} </span>
</div>
You have to use array syntax, because although there is only 1 user object, it is structured in the json as an array ( note the [ ] , same for Email).
Here is a fiddle: http://jsfiddle.net/Lvc0u55v/5593/
UPDATE: For showing only the last email which is true, use ng-if:
<span ng-repeat="email in collection.user[0].Email| filter: {IsPreffered : true} " ng-if="$last"> {{email.Id}}</span>
Here is the updated fiddle:
http://jsfiddle.net/Lvc0u55v/5594/
One way to do this is to use ngRepeat on the user Emails array with a filter to get only those Emails where "IsPreffered": true. The filter compares each object in the Emails array with the filter object (filterEmail). This filter object is defined in the controller - this way you will be able to change by demand or even set it dynamically.
var app = angular.module('app', []);
app.controller('ctrl', function($scope) {
$scope.collection = {
"user": {
"Name": "B. Balamanigandan",
"Email": [{
"Id": "bala#gmail.com",
"IsPreffered": true
}, {
"Id": "mani#gmail.com",
"IsPreffered": false
}]
}
};
$scope.filterEmail = {
"IsPreffered": true
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app" ng-controller="ctrl">
<div>
<h3>Employee Details:</h3>
<span> {{collection.user.Name}} </span>
<span ng-repeat="email in collection.user.Email | filter : filterEmail ">
{{email.Id}}
</span>
</div>
</body>
NOTE: To my understanding it doesn't make much sense to have user as an array inside an object (in my example I removed the inner array). If you insist on keeping this data-schema, you will need to change every reference of collection.user to collection.user[0].
In knockout.js, is it possible to let the right-hand-side of a binding (the value of the binding) be dynamic? For example,
<input data-bind="value: dynamicBinding()"/>
<script type="text/javascript">
var vm = {
dynamicBinding : function() {
return "foo().bar";
},
foo : ko.observable({
bar : ko.observable("hi");
}
};
ko.applyBindings(vm);
</script>
the result should be that the the dynamicBinding function is executed while applying the bindings and the resulting string is used as the binding. The input element should be bound to foo().bar, which is the observable with the value "hi".
If you wonder why I would want this, I am trying to render a dynamic table with knockout, where both the rows and the columns are observableArrays, and I want to allow the column definitions to contain the expression of the binding for that column. I.e., I want to be able to do this:
<table data-bind="foreach: data">
<tr data-bind="foreach: $root.columns">
<td data-bind="text: cellValueBinding()"></td>
</tr>
</table>
<script type="text/javascript">
var vm = {
data: ko.mapping.fromJS([
{title: "Brave New World", author: { name : "Aldous Huxley" },
{title: "1984", author: { name : "George Orwell" },
{title: "Pale Fire", author: { name : "Vladimir Nabokov" }]),
columns: ko.observableArray([
{header: "Title", cellValueBinding: function () { return "$parent.title"; }},
{header: "Author", cellValueBinding: function () { return "$parent.author().name"; }}
])
};
ko.applyBindings(vm);
</script>
As you can see from the example, the column definition knows how to extract the value from the data. The table markup itself is more or less a placeholder. But as far as I can tell, this does not work, due to the way knockout processes the bindings. Are there any other options available?
Thanks.
Solution: I ended up using Ilya's suggestion - I can let cellValueBinding be a function that accepts the row and column as arguments, and returns an observable. This technique is demonstrated in this fiddle.
Use ko.computed for it.
Look on example
JSFiddle
EDIT
In your second example, you can pass $parent value ti the function
<td data-bind="text: cellValueBinding($parent)"></td>
and in model
{header: "Title", cellValueBinding: function (parent) { return parent.title; }},
{header: "Author", cellValueBinding: function (parent) { return parent.author().name; }}
JSFiddle
The Knockout mapping plugin documentation has a section entitled "Uniquely identifying objects using “keys”". This describes how to update part of an object and then only update that part of the display rather than completely replacing the display of all properties of a partially-modified object. That all works splendidly in their simple example, which I have slightly modified here to make my question more clear. My modifications were to:
Replace the object with a corrected name after a 2 second delay.
Highlight the unchanging part of the display, so you can see that it is actually not replaced when the update happens.
1. Simple object (jsFiddle)
<h1 data-bind="text: name"></h1>
<ul data-bind="foreach: children">
<li><span class="id" data-bind="text: id"></span> <span data-bind="text: name"></span></li>
</ul>
<script>
var data = {
name: 'Scot',
children: [
{id : 1, name : 'Alicw'}
]
};
var mapping = {
children: {
key: function(data) {
console.log(data);
return ko.utils.unwrapObservable(data.id);
}
}
};
var viewModel = ko.mapping.fromJS(data, mapping);
ko.applyBindings(viewModel);
var range = document.createRange();
range.selectNode(document.getElementsByClassName("id")[0]);
window.getSelection().addRange(range);
setTimeout(function () {
var data = {
name: 'Scott',
children: [
{id : 1, name : 'Alice'}
]
};
ko.mapping.fromJS(data, viewModel);
}, 2000);
</script>
But what isn't clear to me is how I would achieve the same behavior for a more complex nested data structure. In the following example, I took the above code and wrapped the data in a list. I would like this to behave the same as above, but it doesn't. The whole display is redone because of the change in one property. You can see this because, unlike the above example, the highlighting is lost after the data is updated.
2. More complex nested object (jsFiddle)
<!-- ko foreach: parents -->
<h1 data-bind="text: name"></h1>
<ul data-bind="foreach: children">
<li><span class="id" data-bind="text: id"></span> <span data-bind="text: name"></span></li>
</ul>
<!-- /ko -->
<script>
var data = {
parents: [
{
name: 'Scot',
children: [
{id : 1, name : 'Alicw'}
]
}
]
};
var mapping = {
children: {
key: function(data) {
console.log(data);
return ko.utils.unwrapObservable(data.id);
}
}
};
var viewModel = ko.mapping.fromJS(data, mapping);
ko.applyBindings(viewModel);
var range = document.createRange();
range.selectNode(document.getElementsByClassName("id")[0]);
window.getSelection().addRange(range);
setTimeout(function () {
var data = {
parents: [
{
name: 'Scott',
children: [
{id : 1, name : 'Alice'}
]
}
]
};
ko.mapping.fromJS(data, viewModel);
}, 2000);
</script>
So basically what I'm asking is, how can I make the second example work like the first, given the more nested data structure? You can assume that ids are unique for each child (so if I added another parent besides Scott, his children would start with id=2, etc.).
Interesting observation there and nice write-up. It appears to work if you define a key on the parent as well as the child. Try this fiddle:
http://jsfiddle.net/8QJe7/6/
It defines instantiable view model functions for the parents and children, where the parent constructor does its child mappings.