I'm having the strangest time getting localStorage to work on my local machine. I seem to be losing indexes in the array I built, I've been trying for hours to figure out why with absolutely no luck. I've even tried different ways of building the array.
Here is the component I am loading with a v-for loop. this is working as expected.
home.vue
<tweets
v-for="tweet in tweets"
v-bind:key="tweet.id"
v-bind:tweet="tweet"
></tweets>
And here is the trouble-maker component. It loads a number of tweets that can be voted on. (Feel free to ignore the HTML, I'm not sure it's relevant.)
tweets.vue
<template>
<div class="col-2 d-flex">
<div class="align-self-center ml-3">
<div class="row py-1">
<i
class="fas fa-arrow-up"
style="font-size: 30px"
v-on:click="voteUp"
></i>
</div>
<div class="row py-1">
<i class="fas fa-arrow-down" style="font-size: 30px"></i>
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
localStorage: [],
};
},
props: {
tweet: Object,
},
created: function () {
this.localStorage = JSON.parse(localStorage.storageData);
console.log(this.localStorage);
},
methods: {
voteUp: function () {
axios
.get("/api/vote/2/1")
.then(({ data }) => {
var test = {
"tweet_id": 1,
"vote_status": 1
};
this.localStorage.push(test);
console.log(this.localStorage);
localStorage.setItem("storageData", JSON.stringify(this.localStorage));
console.log("go");
//persist?
})
.catch(function (error) {
alert("Take a screen shot and send this to me." + error);
console.log(error);
});
},
},
};
</script>
So if you take a look at my localStorage variable, I have it being set to the current localStorage found in the browser, however...it's having this strange problem where if I click on the button that triggers the voteUp function, it will sometimes delete some of the indexes in the array. I'm having trouble explaining so I've make a quick video to demonstrate.
https://streamable.com/kkhnrx
as you can see, I'm firing the event and the array starts growing, but as I scroll down to different buttons (remember the tweets are looped) the array will lose a few of it's indexes. I have no idea why this is happening. I assume it has something to do with the component being looped and scope problems?
This looks exactly like race condition. There are multiple component instances that compete over the storage, and it becomes overwritten eventually.
This makes an early copy that won't contain updates from other instances:
this.localStorage = JSON.parse(localStorage.storageData)
this.localStorage should be assigned immediately before updating the storage, or there should be a common state for all component instances (e.g. by using Vuex with persistence).
You have an error in your created function, right?
If you want to get "storageData" from local storage it should be:
created: function () {
this.localStorage = JSON.parse(localStorage.getItem('storageData'));
console.log(this.localStorage);
},
Related
I've run into an extremely strange problem while developing a Vue SPA in Firefox (v89.0.1). Reactive data will not render on the page when it is contained within columns. Here is an example:
<template>
<div style="column-count: 2">
<div style="break-inside: avoid;">
Column 1
</div>
<div style="break-inside: avoid;">
Column 2
<div v-if="test">{{ test }}</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
test: ''
};
},
mounted() {
setTimeout(() => {
this.test = 'tested';
}, 2000);
}
};
</script>
In Chrome, the word "tested" is correctly rendered in the second column. In Firefox the word "tested" does not immediately appear, but it will appear later if the window is resized. The issue is resolved if the column-count style is removed.
Is this a known issue and, if so, does anyone have a suggested work around? Really scratching my head on this. (Note: I'm using column-count to create a masonry layout, so I can't easily substitute another grid solution.)
I am using Laravel and Vue.
When I was searching on the internet I saw the following code.
<template>
<div>
<h3 class="text-center">Create Movie</h3>
<div class="row">
<div class="col-md-6">
<form #submit.prevent="createMovie">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" v-model="movie.name">
</div>
<div class="form-group">
<label>Director</label>
<input type="text" class="form-control" v-model="movie.director">
</div>
<button type="submit" class="btn btn-primary">Create movie</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
movie: {}
}
},
methods: {
createMovie() {
this.axios
.post('http://localhost:8000/api/movie/create', this.movie)
.then(response => (
this.$router.push({name: 'movie'})
))
.catch(error => console.log(error))
.finally(() => this.loading = false)
}
}
}
I am trying to find out what the last line
.finally(() => this.loading = false)
is doing. I am searching on the internet but I can't find what it does. Also, I tried running the code without the last line however, it did not make any change.
Can someone please tell me what this is doing and when it is useful?
Without seeing the associated Vue template we cannot explain what it is doing exactly, however, we can be fairly confident that the value of loading will be used to show/hide some sort of overlay or activity spinner.
The purpose of the overlay/activity spinner is to provide visual feedback to the user that something is happening. This is useful when loading large amounts of data into your page, or when you perform a long running process (such as uploading a large file for example). So rather than the user seeing nothing on first page load, or clicking a button and wondering if it worked, they are provided with something to let them know that something is happening.
A basic example of what this might look like in the Vue template could be:
// if the value of loading is true, show this
<div v-if="this.loading">Loading, please wait ...</div>
// otherwise show this
<div v-else>Other content</div>
Your example is setting the value of loading to false once a response has been received from your axios request. You would probably want to set the value of loading to true prior to making the request to show an overlay/activity spinner.
Uncaught (in promise) ReferenceError: ture is not defined
You have a typo, it should be true not ture.
With the fine-uploader plugin I am trying to add multiple (dynamic could be 1, or 10) instances with an optional caption field and a manual upload button per section.
The form I am uploading from is dynamically generated in layout as well as content, the uploaded files have to be stored by the handler based upon the section of the form as well as the instance of fine-uploader. I also need the ability to effectively upload each instance of fine-uploader independently
The issue that I am hitting is following the guidelines & demo for the manual upload option, ie adding a click function it will always find only the first instance as it searches for the button using .getElementById.
I can get around this by defining a new template for each instance however I would prefer to use a single template.
The template code (for each instance - abbreviated for simplicity) is
<script type="text/template" id="qq-template-manual-trigger#XX#">
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
...
<div class="buttons">
<div class="qq-upload-button-selector qq-upload-button">
<div>Select files</div>
</div>
<button type="button" id="trigger-upload#XX#" class="btn btn-primary">
<i class="icon-upload icon-white"></i> Upload
</button>
</div>
...
<ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
<li>
...
<input class="caption" tabindex="1" type="text">
...
</li>
</ul>
...
</div>
</script>
<div id="fine-uploader-manual-trigger#XX#"></div>
and the uploader script
<script>
var manualUploader#XX# = new qq.FineUploader({
element: document.getElementById('fine-uploader-manual-trigger#XX#'),
template: 'qq-template-manual-trigger#XX#',
request: {
inputName: "imagegroup[]",
endpoint: '/SaveFile.aspx'
},
autoUpload: false,
debug: true,
callbacks: {
onError: function(id, name, errorReason, xhrOrXdr) {
alert(qq.format("Error on file number {} - {}. Reason: {}", id, name, errorReason));
},
onUpload: function (id) {
var fileContainer = this.getItemByFileId(id)
var captionInput = fileContainer.querySelector('.caption')
var captionText = captionInput.value
this.setParams({
"descr[]": captionText,
<-- Other parameters here -->
}, id)
}
},
});
qq(document.getElementById("trigger-upload#XX#")).attach("click", function () {
manualUploader#XX#.uploadStoredFiles();
});
</script>
in the ideal world I would prefer simply have a single
<script type="text/template" id="qq-template-manual-trigger">
....
</script>
then where required multiple times through the form
<div id="fine-uploader-manual-trigger"></div>
<script>
var manualUploader#XX# = new qq.FineUploader({
element: document.getElementById('fine-uploader-manual-trigger'),
template: 'qq-template-manual-trigger',
...
}
qq(document.getElementById("trigger-upload")).attach("click", function () {
manualUploader#XX#.uploadStoredFiles();
});
</script>
The use of the attach function by calling .getElementById just feels wrong, or at the very least cludgy, is there a better way of activating the upload on a per-instance basis?
Thanks in advance
K
Sorted, but if anyone has a better answer...
Instead of using the demo of document.getElementById("trigger-upload")
Simply use document.querySelector("#fine-uploader-manual-trigger #trigger-upload")
eg
<div id="fine-uploader-manual-triggerXX"></div>
<script>
var manualUploaderXX = new qq.FineUploader({
element: document.getElementById('fine-uploader-manual-triggerXX'),
template: 'qq-template-manual-trigger',
... // omitted for brevity
}
qq(document.querySelector("#fine-uploader-manual-triggerXX #trigger-upload")).attach("click", function () {
manualUploaderXX.uploadStoredFiles();
});
</script>
So I am trying to have a sub menu that changes the state of the child view, and I am failing.
So set up the following function to call $state.go
$scope.stateGo = function (state) {
console.log("Loadting state " + state)
$state.go(state);
}
And I can see on the console that the correct (or what I think is the correct state name) is called
Loadting state board.stat
However, nothing at all seems to be happening with the actual router. If I change it so a parent state. It does work. For example, if I set it to board it works.
The files that contains the ui-views looks as follows:
<div ui-view="topmenu">
</div>
</div>
<div id="mainView">
<div ui-view="mainView">
</div>
<div style="height: 100px;"> </div>
</div>
<div class="col-md-offset-3 footer navbar-fixed-bottom" id="adView">
<div ui-view="adView">
</div>
</div>
The state config:
.state('board', {
url: "/view/board",
views: {
topmenu: {templateUrl: "views/partials/menu-board"},
mainView: {templateUrl: "views/partials/welcome"},
adView: {templateUrl: "views/partials/amazon-banner"}
},
//controller: 'testCtrl'
})
.state('board.stat', {
url: "/stat",
views: {
topmenu: {templateUrl: "views/partials/menu-board"},
mainView: {templateUrl: "views/partials/stat"},
adView: {templateUrl: "views/partials/amazon-banner"}
}
})
Am I missing something, should a call to $state.go('board.stat') get ui-router to load stat into mainView? And if so, any idea why it isn't?
======================= EDIT ===================
OK, think I might be doing it wrong, but not certain how...
Changed the buttons to use ui-href
<a ui-sref="board.stat" ui-sref-active="active" class="btn btn-xlarge" ><button class="btn btn-primary btn-circle btn-xl"><i class="fa fa-bar-chart fa-1x"></i><br><h6>Stats</h6></button></a>
<a ui-sref="board.quickbet" ui-sref-active="active" class="btn btn-xlarge" ><button class="btn btn-primary btn-circle btn-xl"><i class="fa fa-plus fa-1x"></i><br><h6>Quick bet</h6></button></a>
So same layout as earlier, but it seems like both child states are loaded ONLY when I enter parent state.
So I added some debugging for the state using the following two functions:
$rootScope.$on('$stateChangeError',
function(event, toState, toParams, fromState, fromParams, error){
console.log("State error: " + error);
})
$rootScope.$on('$viewContentLoading',
function(event, viewConfig){
// Access to all the view config properties.
// and one special property 'targetView'
// viewConfig.targetView
console.log("State event: " + viewConfig)
});
But the only output I get is:
2 State event: mainView#
2 State event: adView#
But when I press the buttons nothing seems to happen
Think I solved it.
Well, I did solve it, but I'm surprised by how it works.
So the problems seems to be that I thought the child state would reload the hole set of ui-views, it does not, and trying to do so seems to do nothing. I have no clue if this is correct interpretation, or expected behaviour, but this is what worked for me.
Load parent state with all three views (named topmenu, mainView and adView) (happens automatically).
Add a new ui-view (in my case in stats.ejs loaded on mainView)
This is your stats and bets
<ui-view></ui-view>
Last thing is to ONLY update the parts of the screen you wan't, so not mainView, but targeting the new ui-view in stats.ejs
.state('board.stat', {
templateUrl: "views/partials/stat"
})
And it works, just not certain if this has to do with the DOM or something, but it worked in my case
I seem to be misunderstanding how to pass data to a Vue.js component with an ajax call.
My understanding of how this should work:
I need to create an empty object called campaigns in the data section of my component.
Then call method "fetchCampaigns" on page ready to replace the data object.
fetchCampaign method completes an AJAX call and inside of the success callback use this.$set('campaigns', campaigns) to replace the empty campaign object with the newly returned campaign object
Use v-for on the template to iterate through the campaign object and access values with #{{campaign.type}}
My html (I am use vue router, vue resource and laravel blade) :
<router-view></router-view>
<template id="campaignBlock" v-for="campaign in campaigns">
<div class="row">
<div class="block">
<div class="block-title">
<h2>Type: <em>#{{campaign.id}}</em></h2>
</div>
<div class="row"><!-- Grid Content -->
<div class="hidden-sm hidden-xs col-md-4 col-lg-4">
<h2 class="sub-header">#{{campaign.title}}</h2>
</div>
</div>
</div><!-- END Grid Content -->
</template>
Vue component
Vue.component('app-page', {
template: '#campaignBlock',
data: function() {
return{
campaigns: []
}
},
ready: function () {
this.fetchCampaigns();
},
methods: {
fetchCampaigns: function () {
var campaigns = [];
this.$http.get('/retention/getCampaigns')
.success(function (campaigns) {
this.$set('campaigns', campaigns);
})
.error(function (err) {
campaigns.log(err);
});
},
}
})
This is the result of my ajax call from console:
{"campaigns":[{"id":1,"user_id":2,"target_id":1,"name":"Test Campaign","description":"This is a test Campaign","target":"Onboarding","created_at":"-0001-11-30 00:00:00","updated_at":"-0001-11-30 00:00:00","deleted_at":null}]}
I'm not sure why I can't get my vue component to recognize the new data. Anyone see what I'm missing? TIA
Turns out that v-for="campaign in campaigns" should not go on the template tag, but inside of it.
So this:
<template id="campaignBlock" v-for="campaign in campaigns">
<div class="row">
Should be changed to this:
<template id="campaignBlock">
<div class="row" v-for="campaign in campaigns">