Vuejs databinding on image source not working - javascript

I have the following component where I read data from Indexeddb (ArrayBuffer) and use it as source for an image. When the parent component uses this component, only the last formUpload gets it's dataSource set. My console.log's tells me that the data is there, the URL.createobjecturl is succesfully created, I can open them in devtools and see the images, but they are not assigned as the source of the image. Any ideas?
<div class="ui grid segment" v-if="formData.Uploads.length > 0">
<div class="four wide column" v-for="upload in formData.Uploads" style="position:relative;">
<formUpload :upload="upload"></formUpload>
</div>
</div>
Vue.component("formUpload", {
props: ["upload"],
template: `
<div class="ui fluid image">
<a class="ui green left corner label">
<i class="add icon"></i>
</a>
<a class="ui red right corner label" v-on:click="removeUpload(upload)">
<i class="remove icon"></i>
</a>
<img style="width:100%;" :src="dataSource" />
<div class="ui blue labels" v-if="upload.tags.length > 0" style="margin-top:5px;">
<image-tag v-for="tag in upload.tags" :tagtext="tag" :key="tag" :upload="upload.id" v-on:deletetag="deletetag"></image-tag>
</div>
</div>
`,
data: function () {
return {
dataSource: undefined
}
},
mounted: function () {
_this = this;
imageStore.getItem(_this.upload.imageId).then(function (result) {
console.log("Data gotten", result.data);
var dataView = new DataView(result.data);
var blob = new Blob([dataView], { type: result.type });
console.log("Data transformed", blob);
_this.dataSource = URL.createObjectURL(blob);
console.log("DataUrl", _this.dataSource);
});
},
methods: {
removeUpload: function (upload) {
console.log("removeUpload");
}
}
});

In short, you need a key.
When Vue is updating a list of elements rendered with v-for, it by
default uses an “in-place patch” strategy. If the order of the data
items has changed, instead of moving the DOM elements to match the
order of the items, Vue will simply patch each element in-place and
make sure it reflects what should be rendered at that particular
index. This is similar to the behavior of track-by="$index" in Vue
1.x.
This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM
state (e.g. form input values).
[...]
It is recommended to provide a key with v-for whenever possible, unless the iterated DOM content is simple, or you are intentionally relying on the default behavior for performance gains.
This is a fairly common "gotcha" which should be helped by the fact that
In 2.2.0+, when using v-for with a component, a key is now required.

Related

apply function from methods to reverse string in paragraph in vue.js

Dears, I have tried to apply function to reverse string in paragraph text in vue.js,
I have created function to reverse words in methods called (reverseword) and added it card using :rule="reverseword()",but it does not work. your support is highly appreciated
Code:
<div class="post-box">
<span class="post-viwes">{{viwes}}</span>
<h3 class="post-title">{{title}}</h3>
<span class="post-date">{{date}}</span>
<p class="post-content">{{content}}</p>
<div class="row">
<div class = "col-sm-6 text-right">
<span class="post-author">{{author}} </span>
</div>
<div class = "col-sm-6 text-right" :rules="reverseword()">
<span class="post-category" >{{category.toUpperCase()}}</span>
</div>
</div>
)
</div>
</template>
<script>
export default {
props:["viwes","title","date","content","author","category"],
name:"posts",
methods: {
reverseWord: function () {
this.category = this.category.split('').reverse().join('')
}
}};
</script>```
Your reverseWord method attempts to mutate a prop (category).
You can't mutate a prop, because the mutation would be overwritten by the first update from the parent.
If/when you do want/need to change a prop value, you have do it in the parent component which will then pass the change down to the child component, through the props binding.
Updating the parent from child can be done by
either using $emit
or by using a store, external to both the child and the parent.
If, in fact, you don't want to mutate category, you just need to be able to use its reverse inside the template, create a computed property:
computed: {
reversedCategory() {
return this.category.split('').reverse().join('');
}
}
Use it in template as you would use a normal property:
<div class = "col-sm-6 text-right" :rules="reversedCategory">
<span class="post-category" >{{category.toUpperCase()}}</span>
</div>
The computed is reactive. Meaning every time category changes, reverseCategory will update accordingly.

How to make ko.sortable work with ko.computed

I am using knockout and Ryan Niemeyers ko.sortable.
My aim is to move items from one container to another. This works nice.
However, due to other circumstances I want the observableArrays object to be drag´n´dropped to be ko.computed.
I have a basic array containing all my elements
self.Textbatches = ko.observableArray();
then I have a filter version
self.TextsTypeOne = ko.computed(function(){
return ko.utils.arrayFilter(self.Textbatches(),function(t){
return t.type() === '1';
});
});
I have another filtered list with the texts dragging:
self.allocationableTextbatches = ko.computed(function(){
return ko.utils.arrayFilter(self.Textbatches(),function(t){
return t.type() === null;
});
});
Then I create my two containers with ´self.TextsTypeOne´ and ´self.allocationableTextbatches´:
<div id="allocationable-texts" class="menupanel">
<h4 class="select">Text(s) to allocate:</h4>
<div class="tb-table">
<div class="tb-list" data-bind="sortable:{template:'textbatchTmpl',data:$root.allocationableTextbatches,allowDrop:true}"></div>
</div>
</div>
<div id="TypeOne-texts" class="menupanel">
<h4 class="select">Text(s) on scene:</h4>
<div class="tb-table">
<div class="tb-list" data-bind="sortable:{template:'textbatchTmpl',data:$root.TextsTypeOne,allowDrop:true}"></div>
</div>
</div>
where
<script id="textbatchTmpl" type="text/html"><div class="textbatch" data-bind="click: $root.selectedText">
<span class="tb-title" data-bind="text: title"></span>
I can easily add buttons to my template setting type to 'null' and '1' respectively, and make it work that way. However I want this to be a drag and drop feature (as well) and the ko.sortable works on the jquery-ui objects. Thus it needs ´ordinary´ ´ko.observableArrays´ and not ´ko.computed´s as it is 'splice´-ing the observableArray, and dragging and dropping results in an error "TypeError: k.splice is not a function".
I have tried to make ´self.allocationableTextbatches´ and ´self.TextsTypeOne´ writable computed, but to no avail.
Any help and suggestions is highly appreciable!
Thanks in advance!

How to dynamically bind a WinJS list view within a repeater

I have a WinJS Repeater which is using a template. That template contains a WinJS list view. I can't seem to figure out how to use data-win-bind to set the inner list views itemDataSource.
My Repeater:
<section id="genreView" aria-label="Main content" role="main">
<div id="genreWrapper">
<div id="genreRows" data-win-control="WinJS.UI.Repeater"
data-win-options="{template: select('#genreRowTemplate')}">
</div>
</div>
</section>
My Template which contains a List View:
<div id="genreRowTemplate" data-win-control="WinJS.Binding.Template">
<div id="genreRow">
<h2 class="genreTitle" data-win-bind="innerText: genreTitle"></h2>
<div class="genreMovieListView"
data-win-control="WinJS.UI.ListView"
data-win-bind="itemDataSource : data"
data-win-options="{ layout: { type: WinJS.UI.GridLayout },
itemsDraggable: false,
selectionMode: 'single',
tapBehavior: 'none',
swipeBehavior: 'none',
itemsReorderable: false,
itemTemplate: select('#genreMovieTemplate')}">
</div>
</div>
</div>
My List View's template:
<div id="genreMovieTemplate" data-win-control="WinJS.Binding.Template">
<div class="genreMovieItem">
<img style="width: 175px; height: 250px;" data-win-bind="alt: title; src: posterUrl" />
</div>
</div>
The data for the repeater is similar to this (only it's a binding list):
var repeaterData = [{genreTitle: "titleA", data: new WinJS.Binding.List() },
{genreTitle: "titleB", data: new WinJS.Binding.List() }
{genreTitle: "titleC", data: new WinJS.Binding.List() }
{genreTitle: "titleD", data: new WinJS.Binding.List() }];
The data is created on the fly and each binding list is actually a lot more data so I can't get a good sample of real data.
What I DO get is a repeated control, repeated exactly the number of times as the records in my bound list. What I DON'T get is the binding list displayed. My inner binding list is not getting databound via the data-win-bind. I've tried a few things and I either get nothing or an error. Any help is appreciated. Thanks.
EDIT: I think the right way to bind would be data-win-bind="data-win-options.itemDataSource: data", but doing that throws the following confusing error: "JavaScript runtime error: WinJS.UI.Repeater.AsynchronousRender: Top level items must render synchronously".
I was able to solve my problem, but I don't think I went about it in the right way. I went ahead and set the data source for the repeater, which gives me my genreRows from above. This was always working, it was just the child dataSource I couldn't figure out how to get to. My solution... set it in code after it loads. Nothing fancy, just a solution that feels a bit hacky to me. Here's my code in case it helps someone else get past this problem.
var titleList = $(".genreRow > .genreTitle");
var genreLists = $(".genreRow > .genreMovieListView");
for (var i = 0; i < genreLists.length; i++) {
var title = titleList[i].innerText;
var listControl = genreLists[i].winControl;
tempData.forEach(function (element, i) {
if (element.genreTitle == title)
listControl.itemDataSource = element.data.dataSource;
});
};
Basically the above code assumes the title is unique (which it is). So it uses this as a key to traverse the data and set the itemDataSource that way.
I won't mark this as the accepted answer just in case someone can point out how to do this "right".
Old question, but still deserves a good answer:
I've modified your HTML to show the correct binding syntax.
<div id="genreRowTemplate" data-win-control="WinJS.Binding.Template">
<div id="genreRow">
<h2 class="genreTitle" data-win-bind="innerText: genreTitle"></h2>
<div class="genreMovieListView"
data-win-control="WinJS.UI.ListView"
data-win-bind="winControl.itemDataSource : data.dataSource"
data-win-options="{ layout: { type: WinJS.UI.GridLayout },
itemsDraggable: false,
selectionMode: 'single',
tapBehavior: 'none',
swipeBehavior: 'none',
itemsReorderable: false,
itemTemplate: select('#genreMovieTemplate')}">
</div>
</div>
Notice the winControl.itemDataSource and data.dataSource. Any WinJS control property that is not a standard HTML element property needs the winControl property in front of whatever property you are databinding. So the onclick property of a button element doesn't need it, but WinJS.UI.Command has icon which is not on the underlying button so needs the winControl prefix.
Also, ListView controls bind to a WinJS.Binding.List's dataSource property, not the List itself. For some reason, a repeater binds to the ListView itself however.

Getting Meteor 0.9.1.1 click event to update object

I'm just playing around with different patterns and am very new to programming, however I've got everything to work in my test app so far except this. I've tried a bunch of variations with no luck, but I suspect I'm missing something really simple.
Basically what I want to happen is for a user to click a button and for it to then update the value of two specific attributes of the current object.
In this example I'm wanting the update to occur when the user clicks the "Return" button (the other buttons shown below are working fine).
Here's the HTML template for the button in question:
<template name="bookDetails">
<div class="post">
<div class="post-content">
<h3>{{title}}</h3><span> {{author}}</span>
{{#if onLoan}}
<i class="fa fa-star"></i>
On loan to: {{lender}}{{/if}}
</div>
{{#if ownBook}}
Edit
Lend
<div class="control-group">
<div class="controls">
<a class="discuss btn return" href="">Return </a>
</div>
</div>
{{/if}}
</div>
</template>
Here's the .js file which contains my Template event. Basically I want to set the values for the "lendstatus" and "lender" attributes.
Template.bookDetails.helpers({
ownBook: function() {
return this.userId == Meteor.userId();
},
onLoan: function() {
return this.lendstatus == 'true';
}
});
Template.bookLoan.events({
'click .return': function(e) {
e.preventDefault();
var currentBookId = this._id;
var bookProperties = {
lendstatus: "false",
lender: "",
}
Books.update(currentBookId, {$set: bookProperties}, function(error) {
if (error) {
// display the error to the user
throwError(error.reason);
} else {
Router.go('bookPage', {_id: currentBookId});
}
});
},
});
If I type the following into the Browser console while on the page for the object with id ZLDvXZ9esfp8yEmJu I get the correct behaviour on screen and the database updates so I know I'm close:
Books.update({ _id: "ZLDvXZ9esfp8yEmJu"}, {$set: {lendstatus: "false", lender: ""}});
What am I missing?
OK - so my problem was that I'd defined the event handler in the wrong template. I'd defined it in the bookLoan template instead of the bookDetails template. Thanks #saimeunt for pointing this out!

I have two divs with the same ng-controller in AngularJS, how can I make them share information?

I have two different div tags in my html code referencing the same controller in AngularJS. What I suspect is that since these divs aren't nested they each have their own instance of the controller, thus the data is different in both.
<div ng-controller="AlertCtrl">
<ul>
<li ng-repeat="alert in alerts">
<div class="span4">{{alert.msg}}</div>
</li>
</ul>
</div>
<div ng-controller="AlertCtrl">
<form ng-submit="addAlert()">
<button type="submit" class="btn">Add Alert</button>
</form>
</div>
I know this could easily be fixed by including the button in the first div but I feel this is a really clean and simple example to convey what I am trying to achieve. If we were to push the button and add another object to our alerts array the change will not be reflected in the first div.
function AlertCtrl($scope) {
$scope.alerts = [{
type: 'error',
msg: 'Oh snap! Change a few things up and try submitting again.'
}, {
type: 'success',
msg: 'Well done! You successfully read this important alert message.'
}];
$scope.addAlert = function() {
$scope.alerts.push({
type: 'sucess',
msg: "Another alert!"
});
};
}
This is a very common question. Seems that the best way is to create a service/value and share between then.
mod.service('yourService', function() {
this.sharedInfo= 'default value';
});
function AlertCtrl($scope, yourService) {
$scope.changeSomething = function() {
yourService.sharedInfo = 'another value from one of the controllers';
}
$scope.getValue = function() {
return yourService.sharedInfo;
}
}
<div ng-controller="AlertCtrl">{{getValue()}}</div>
<div ng-controller="AlertCtrl">{{getValue()}}</div>
If I understand the question correctly, you want to sync two html areas with the same controller, keeping data synced.
since these divs aren't nested they each have their own instance of the controller, thus the data is different in both
This isn't true, if you declare the controllers with the same alias (I'm using more recente angular version):
<div ng-controller="AlertCtrl as instance">
{{instance.someVar}}
</div>
<div ng-controller="AlertCtrl as instance">
{{instance.someVar}} (this will be the same as above)
</div>
However, if you WANT them to be different and comunicate each other, you will have to declare different aliases:
<div ng-controller="AlertCtrl as instance1">
{{instance1.someVar}}
</div>
<div ng-controller="AlertCtrl as instance2">
{{instance2.someVar}} (this will not necessarily be the same as above)
</div>
Then you can use services or broadcasts to comunicate between them (the second should be avoided, tough).

Categories