I'm trying to show items in a table every time the list of item changes. I've got a function and a computed but I'm not quiet sure if I'm doing it right.
Function(){
Every time a new value comes in: changeList.push(item);
}
Then I have this computed
Computed:
changedData: function(){
return this.changeList;
}
HTML
<tr v-for="change in changedData">
<td>change.value</td>
<tr>
It does print out every item in the list when it is changed, but the thing is I don't want it to print out the items that already printed out, only new items.
EDIT: Found the problem (push function)
for(index in question.Answers){
if(question.Answers[index].Selected === "selected" &&
question.Answers[index].Value === savedQuestion.Value){
this.myList.push({newValue: question.Answers[index].Value, oldValue:
savedQuestion.Value});
}
}
This will add all the questions with their value regardless if the list already contains the same question with the exact same values.
So to achieve this you can check in the array if the object with same values is present or not.If not than only push the values.
Below is the updated code.
const search = (newVal,oldVal) => array.find(element => (element.newValue === newVal && element.oldValue === oldVal));
for(index in question.Answers){
if(!search(question.Answers[index].Value,savedQuestion.Value))
if(question.Answers[index].Selected === "selected" &&
question.Answers[index].Value === savedQuestion.Value){
this.myList.push({newValue: question.Answers[index].Value, oldValue:
savedQuestion.Value});
}
}
In your code computed is actually not needed.Here is the basic example of adding dynamic values into the list at runtime.
https://codepen.io/anon/pen/LqKjMM?editors=1111
Template code =>
<script src="//vuejs.org/js/vue.js"></script>
<h1>Example of managing a Vue.js list</h1>
<div id="products">
<vue-products></vue-products>
</div>
<script type="text/template" id='vue-products-template'>
<div>
<form v-on:submit.prevent="addProduct">
<input type="text" v-model="productName">
<input type="submit" value="Add"></input>
</form>
<ul>
<li v-for="name in productNames">{{name}}</li>
</ul>
</div>
</script>
Script code =>
Vue.component('vue-products', {
template: '#vue-products-template',
data: function() {
return {
productName: "",
productNames: ['booze', 'tums', 'peas', 'short wave radio'],
}
},
methods: {
addProduct: function() {
this.productNames.push(this.productName)
this.productName = ""
}
}
})
$(function() {
new Vue({el: '#products', data: {}})
});
Related
I am trying to filter through an array and display data based on the click event. There are three buttons that control different actions.
1.Button rollers display all the individuals who are rolling
2.Buttons non-rollers display lazy peeps who don't roll.
3.Buttons All display all the rollers and non-rollers.
I am trying to figure out if there is any way to display all rollers with the same filterRollers() function. I understand just resetting the state of rollingData back to the original value in a different function would do the trick but I am trying to limit to using one function. I will appreciate Any suggestion regarding the best practices . Thank you
var data =[
{ name="adam", task="roll" ,isrolling=true},
{ name="jack", task="roll" ,isrolling=false},
{ name="r2d2", task="roll" ,isrolling=true},
{ name="spidy", task="roll" ,isrolling=false}
]
this.state={
rollingState=data,
rollingData=data
}
filterRollers(status) {
let list = this.state.rollingState.filter(roll => {
return roll.isrolling === status
})
this.setState({
rollingData:list
})
}
render(){
return(
<div>
<button onClick={this.filterRollers(true)}>rollers </button>
<button onClick={this.filterRollers(false)}>non-rollers </button>
<button onClick={this.filterRollers(something)}> All </button>
{this.state.rollingData.map(roll=>{
return <Something roll={roll}/>
}
)}
</div>
)
}
When you want to show all the rollers, you can just call the function without any parameters. And then at the beginning of filterRollers function you can check if the parameter is undefined. If it is, just return all the data. If not, filter:
filterRollers(status) {
let list;
if(typeof status !== 'undefined'){
list = this.state.rollingState.filter(roll => {
return roll.isrolling === status
})
} else{
list = data.slice();
}
this.setState({
rollingData:list
})
}
Call the function like this, when you want to show all the rollers:
<button onClick={this.filterRollers()}> All </button>
I have a list of items that are checkboxes for a settings page. These are stored in a const items like so:
const ITEMS = [
["youtube", "YouTube"],
["videos", "Videos"],
["facebook", "Facebook"],
["settings", "Settings"],
]
and currently, these four items are just listed as list items. What I have right now is this:
but what I want is this:
I was thinking of applying a check to see if the sub-category belongs to that parent category, and apply some type of indentation to the sub-categories. Is that possible to do through a map (which is how I'm iterating through this array)? Or is there a smarter, more efficient way to solve this issue?
Here's how I render my checkboxes:
item: function(i) {
return (
<div className="checkbox">
<label>
<input type="checkbox" checked={this.state.items[i[0]]}/>
{i[1]}
</label>
</div>
)
}
render: function() {
return (
<div className="col-sm-12">
{_(ITEMS).map(this.item, this)}
</div>
)
}
I would recommend you to use objects in an array. So you're able to map more than just property to each item. For accessing it only with normal Javascript I would use a for-Loop because of its compability with older browsers. An example I made, can be found here:
https://jsfiddle.net/morrisjdev/vjb0qukb/
With some 'dirty' code, you could use this: https://jsfiddle.net/1sw1uhan/
const ITEMS = [
["youtube", "YouTube"],
["videos", "Videos"],
["facebook", "Facebook"],
["settings", "Settings"],
]
var list = $('<ul>');
$.each(ITEMS, function( index, value ) {
if ((index+1)%2 == 0 && index != 0){
var sublist = $('<ul>').append($('<li>').text(value[1]).attr('id', value[0]));
} else {
var sublist = $('<li>').text(value[1]).attr('id', value[0]);
}
list.append(sublist);
})
$('#somediv').append(list);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="somediv">
</div>
I am starting with a simple TODO app with Aurelia, RethinkDB & Socket.IO. I seem to have problem with re-rendering or re-evaluating an object that is changed through Socket.IO. So basically, everything works good on the first browser but doesn't get re-rendered in the second browser while displaying the object in the console does show differences in my object. The problem is only on updating an object, it works perfectly on creating/deleting object from the array of todo items.
HTML
<ul>
<li repeat.for="item of items">
<div show.bind="!item.isEditing">
<input type="checkbox" checked.two-way="item.completed" click.delegate="toggleComplete(item)" />
<label class="${item.completed ? 'done': ''} ${item.archived ? 'archived' : ''}" click.delegate="$parent.editBegin(item)">
${item.title}
</label>
<i class="glyphicon glyphicon-trash"></i>
</div>
<div show.bind="item.isEditing">
<form submit.delegate="$parent.editEnd(item)">
<input type="text" value.bind="item.title" blur.delegate="$parent.editEnd(item)" />
</form>
</div>
</li>
</ul>
NodeJS with RethinkDB changefeeds
// attach a RethinkDB changefeeds to watch any changes
r.table(config.table)
.changes()
.run()
.then(function(cursor) {
//cursor.each(console.log);
cursor.each(function(err, item) {
if (!!item && !!item.new_val && item.old_val == null) {
io.sockets.emit("todo_create", item.new_val);
}else if (!!item && !!item.new_val && !!item.old_val) {
io.sockets.emit("todo_update", item.new_val);
}else if(!!item && item.new_val == null && !!item.old_val) {
io.sockets.emit("todo_delete", item.old_val);
}
});
})
.error(function(err){
console.log("Changefeeds Failure: ", err);
});
Aurelia code watching Socket.on
// update item
socket.on("todo_update", data => {
let pos = arrayFindObjectIndex(this.items, 'id', data.id);
if(pos >= 0) {
console.log('before update');
console.log(this.items[pos]);
this.items[pos] = data;
this.items[pos].title = this.items[pos].title + ' [updated]';
console.log('after update');
console.log(this.items[pos]);
}
});
// create item, only add the item if we don't have it already in the items list to avoid dupes
socket.on("todo_create", data => {
if (!_.some(this.items, function (p) {
return p.id === data.id;
})) {
this.items.unshift(data);
}
});
// delete item, only delete item if found in items list
socket.on("todo_delete", data => {
let pos = arrayFindObjectIndex(this.items, 'id', data.id);
if(pos >= 0) {
this.items.splice(pos, 1);
}
});
The socket.on("todo_update", ...){} is not making the second browser re-render but showing the object in the console before/after update does show differences in the object itself. I even changed the todo title property and that too doesn't get re-rendered.
How can I get Aurelia to re-render in my second browser with the new object properties? Don't be too hard on me, I'm learning Aurelia/RethinkDB/NodeJS/Socket.IO all the same time...
Aurelia observes changes to the contents of an array by overriding the array's mutator methods (push, pop, splice, shift, etc). This works well for most use-cases and performs really well (no dirty-checking, extremely lightweight in terms of memory and cpu). Unfortunately this leaves one way of mutating an array that aurelia can't "see": indexed assignment... eg myArray[6] = 'foo'. Since no array methods were called, the binding system doesn't know the array changed.
In your case, try changing this:
// update item
socket.on("todo_update", data => {
let pos = arrayFindObjectIndex(this.items, 'id', data.id);
if(pos >= 0) {
console.log('before update');
console.log(this.items[pos]);
this.items[pos] = data; // <-- change this to: this.items.splice(pos, 1, data);
this.items[pos].title = this.items[pos].title + ' [updated]';
console.log('after update');
console.log(this.items[pos]);
}
});
I have a view (cshtml) that has a tab strip on it. The contents of each tab is of course different. The individual tabs have the correct data/information on them. There is some javascript that is intended to fire when a selection is made from the control on the individual tab. As it stands right now the first tab rendered the javascript fires. All other tabs do not fire. Further on the tab that does fire (first one) it obtains the correct value but then when trying to find the matching item in the model it doesn't find a match. Debugging shows that only the data for the last tab is available in the model. Well that explains why no match but begs the question of where did the data the first page was populated with go?
I have snipped the code for brevity. If, in my ignorance I left something out just say so and I'll post whatever is needed.
So to start here is the parent cshtml:
foreach (var extbrd in Model.ExternalBoards)
{
tabstrip.Add()
.Text(extbrd.ExtForumName)
.ImageUrl("~/.../ForumTabIcon.png")
.Content(#<text>
<div>
#Html.Action("ActionName", "Controller", new { id = extbrd.BoardId });
</div>
</text>);
}
Well as you can see above as we loop we call an action in the controller for each tab. Here is that action:
public ActionResult ActionName(int extforumid)
{
//get url for selected forum (tab) and pull feed
ExternalForums ExtFrm = _forumService.GetExternalForumById(extforumid);
reader.Url = ExtFrm.ForumUrl;
return View(reader.GetFeed());
}
That's actually it. As above I can post the reader code but I don't think it is the source of the trouble.
Well this action of course has a view and this is where I think things get wacky:
#model ExternalThreadsModel
<script type="text/javascript">
var model = #Html.Raw(Json.Encode(Model.RssThreads))
</script>
<script type="text/javascript">
$(function() {
$("##Html.FieldIdFor(model => model.ExtForumIds)").click(function () {
var selectedItem = $(this).val();
var matchingObj = getObjects(model, 'ThreadValue', selectedItem);
if(matchingObj > 0)
{
var $iframe = $('#ForumFrame');
if ( $iframe.length ) {
$iframe.attr('src', matchingObj[0].Link);
}
var $prevfram = $('#ForumPreview');
if ( $prevfram.length ) {
$prevfram.val(matchingObj[0].Description);
}
}
});
});
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i == key && obj[key] == val) {
objects.push(obj);
}
}
return objects;
}
</script>
<div>
<table>
<tr>
<td>
#Html.DropDownListFor(model => model.ExtForumIds, Model.SelectThreads, new {style = "...", #size = 30})
</td>
<td style="width:25px;"> </td>
<td>
#{ Html.Telerik().TabStrip()
.Name("ForumView")
.Items(tabstrip =>
{
tabstrip.Add()
.Text("Preview")
.Content(#<text>
<div>
<textarea style="background-color:#979797; text-decoration: none;" id="ForumPreview" name="ForumPreview" rows="26" cols="200" readonly></textarea>
</div>
</text>);
tabstrip.Add()
.Text("Interactive")
.Content(#<text>
<div>
<iframe id="ForumFrame" name="ForumFrame" src="" style="width:800px;height:350px;"></iframe>
</div>
</text>);
})
.SelectedIndex(0)
.Render();
}
</td>
</tr>
</table>
</div>
So as I mentioned each tab does have the correct data / information on it. The problem comes when a user selects an item from the drop down list.
The click handler only fires on the first tab. It doesn't fire for any other tabs???
Further on the first tab the click handler does fire and it pulls the correct selectedItem but when it runs through the helper function getobjects it doesn't find a match.
When I break and examine "model" as it is being passed into getObjects it only contains data for the last tab...so yeah nothing is going to be matched.
What is even stranger for me to understand is the line:
<script type="text/javascript">
var model = #Html.Raw(Json.Encode(Model.RssThreads))
</script>
In HTML it does render a json object with ALL the data from ALL the tabs...so...somewhere I must be running into variable scope pollution????
Your support and assistance is..as always..greatly appreciated.
I am trying to filter my users observableArray which has a nested keywords observableArray
based on a keywords observableArray on my viewModel.
When I try to use ko.utils.arrayForEach I get a stack overflow exception. See the code below, also posted in this jsfiddle
function User(id, name, keywords){
return {
id: ko.observable(id),
name: ko.observable(name),
keywords: ko.observableArray(keywords),
isVisible: ko.dependentObservable(function(){
var visible = false;
if (viewModel.selectedKeyword() || viewModel.keywordIsDirty()) {
ko.utils.arrayForEach(keywords, function(keyword) {
if (keyword === viewModel.selectedKeyword()){
visible = true;
}
});
if (!visible) {
viewModel.users.remove(this);
}
}
return visible;
})
}
};
function Keyword(count, word){
return{
count: ko.observable(count),
word: ko.observable(word)
}
};
var viewModel = {
users: ko.observableArray([]),
keywords: ko.observableArray([]),
selectedKeyword: ko.observable(),
keywordIsDirty: ko.observable(false)
}
viewModel.selectedKeyword.subscribe(function () {
if (!viewModel.keywordIsDirty()) {
viewModel.keywordIsDirty(true);
}
});
ko.applyBindings(viewModel);
for (var i = 0; i < 500; i++) {
viewModel.users.push(
new User(i, "Man " + i, ["Beer", "Women", "Food"])
)
}
viewModel.keywords.push(new Keyword(1, "Beer"));
viewModel.keywords.push(new Keyword(2, "Women"));
viewModel.keywords.push(new Keyword(3, "Food"));
viewModel.keywords.push(new Keyword(4, "Cooking"));
And the View code:
<ul data-bind="template: { name: 'keyword-template', foreach: keywords }"></ul><br />
<ul data-bind="template: { name: 'user-template', foreach: users }"></ul>
<script id="keyword-template" type="text/html">
<li>
<label><input type="radio" value="${word}" name="keywordgroup" data-bind="checked: viewModel.selectedKeyword" /> ${ word }<label>
</li>
</script>
<script id="user-template" type="text/html">
<li>
<span data-bind="visible: isVisible">${ $data.name }</span>
</li>
</script>
Your isVisible dependentObservable has created a dependency on itself and is recursively trying to evaluate itself based on this line:
if (!visible) {
viewModel.users.remove(this);
}
So, this creates a dependency on viewModel.users, because remove has to access the observableArray's underlying array to remove the user. At the point that the array is modified, subscribers are notified and one of the subscribers will be itself.
It is generally best to not change the state of any observables in a dependentObservable. you can manually subscribe to changes to a dependentObservable and makes your changes there (provided the dependentObservable does not depend on what you are changing).
However, in this case, I would probably instead create a dependentObservable at the viewModel level called something like filteredUsers. Then, return a version of the users array that is filtered.
It might look like this:
viewModel.filteredUsers = ko.dependentObservable(function() {
var selected = viewModel.selectedKeyword();
//if nothing is selected, then return an empty array
return !selected ? [] : ko.utils.arrayFilter(this.users(), function(user) {
//otherwise, filter on keywords. Stop on first match.
return ko.utils.arrayFirst(user.keywords(), function(keyword) {
return keyword === selected;
}) != null; //doesn't have to be a boolean, but just trying to be clear in sample
});
}, viewModel);
You also should not need the dirty flag, as dependentObservables will be re-triggered when any observables that they access have changed. So, since it accesses selectedKeyword, it will get re-evaluated whenever selectedKeyword changes.
http://jsfiddle.net/rniemeyer/mD8SK/
I hope that I properly understood your scenario.