Vue.js dynamic element creation with localStorage - javascript

I'm completely new to Vue and I can't understand the proper way to render components with dynamic values in Vue.js.
So I have this code below :
new Vue({
el: "#notes",
data: {
title: "",
body: ""
},
methods: {
add: function() {
localStorage.setItem($("#title").val(), $("#body").val());
location.reload(true);
},
clear: function() {
localStorage.clear();
location.reload(true);
}
},
created: function() {
for (i = 0; i < localStorage.length; i++) {
this.title = localStorage.key(i);
this.body = localStorage.getItem(localStorage.key(i));
}
}
});
<div id="notes">
<div class="container">
<div class="form-group">
<label for="title">Enter title</label>
<input class="form-control" id="title"/>
</div>
<div class="form-group">
<label for="body">Enter body</label>
<textarea class="form-control" id="body"></textarea>
</div>
<div class="form-group">
<button class="btn btn-primary" #click="add">Add</button>
</div>
<div class="card" style="width:18rem;" v-for="i in localStorage">
<div class="card-body">
<h5 class="card-title">{{title}}</h5>
<p class="card-text">{{body}}</p><a class="card-link" #click="clear">Delete</a>
</div>
</div>
</div>
</div>
And I can't figure out why does this code renders the elements with the same values, I mean if I have two values in localStorage it renders two elements with the value of the last one on localStorage.
Maybe there's some problem with the loop on created? I need some help with understanding the Vue rendering and fixing my function
CodePen
Thank you very much for spending your precious time with my issue! Thank you for any help!

You have one title variable and one body variable. It can't store two values in one variable, so the second overwrites the first. You need arrays.

Related

In Knockout how do you go about nesting custom components when using an html binding?

I'm a bit new to knockout. I'm trying to get a custom component to dynamically load another custom component. I have a variable called location_board that contains html and that html has a custom component in it. . When I use the data-bind="html: location_board" it put the line for the in the dom but it doesn't run the custom component to fill out that node. Note: If I add the npc-widget directly to the template it works. It just doesn't work when it is added though the html binding. From my research I think this means I need to applyBindings on it? I'm not sure how to go about that in this situation though.
Any help is apricated.
Here is my full code for the custom component.
import {Database} from './database.js'
let database = new Database();
import {ResourceManager} from "./resource-manager.js";
let resourceManager = new ResourceManager();
let locationRegister = {
fog_forest: {
name: "The Ghostly Woodland",
image: "url('img/foggy_forest.jpeg')",
description: `
Place holder
`,
location_board: `
<npc-widget id="john-npc" params="id: 1, tree: 'shopkeep', speed: 50"></npc-widget>
<div>In</div>
`
}
};
ko.components.register('location-widget', {
viewModel: function (params) {
let self = this;
self.function = function () {
console.log("Functions!")
}
for(let k in locationRegister[params.id]) {
console.log(k)
this[k] = locationRegister[params.id][k];
}
console.log(this.name)
//return { controlsDescendantBindings: true };
},
template:
`
<div class="row">
<div class="col-lg-12">
<h2 id="title" class="tm-welcome-text" data-bind="html: name"></h2>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div data-bind="style: { 'background-image': image}" class="location-picture mx-auto d-block">
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="location-description mx-auto d-block" data-bind="html: description"></div>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-12">
<div id="location_board" data-bind="html: location_board">
</div>
</div>
</div>
`
});

Updating unrelated Vue.js variable causing input values in template to disappear

I am having a weird issue with my single file Vue component where when I update an unrelated variable (Vue.js variable), all of my inputs (stuff I typed in, not the elements themselves.) disappear.
I have worked with Vue single file components for a few months now and I have never ran into something like this. Here is the weird part, the variable gets updated successfully as expected, but if I include the variable inside of the template at all that is when all the inputs disappear.
The function is looking up 'agents', then letting the user know how many records have been found and whether or not he/she would like to view them. If the user clicks on the "View" link, then they are shown a bootstrap-modal which shows them the records so that they could select one.
Here is what I have already tried:
Removing all ids from the inputs and using only refs="" to get the values.
changing the 'agents' variable name. Thought maybe it was conflicting with some rogue global or something.
Double checked that the parent component and this component was not being re-rendered. I did that by putting console.log() comments in the mounted() function and as expected it is only rendering once.
Watched the key using Vue dev tools extension to make sure the key was not being changed somehow.
Executed the searchAgent() function in a setTimeout(()=>{},5000) to see whether my use of _.debounce was causing issues.
Used jquery to fetch the values from the inputs instead of refs.
Assign the new records to a local variable agentsArray, then pass that into a function which assigns it to the vue variable 'agents' (its basically a needlessly longer route to the same thing but I thought WHY NOT TRY IT)
Double checked all my uses of 'this' to make sure that I was not accidentally using the wrong this and causing some unknown bug.
Using V-model, but using that doesn't help because I would still have to include the 'agents' inside of the modal in the template.
Using a v-if statement to render the modal HTML in the template only after 'agents' is not an empty array.
Update: Based on a suggestion, removed the function from inside of $(document).ready() inside of the mounted() function.
Template:
<template>
<div class="Q mb-0">
<i class="far fa-question-circle"></i>
<center>
<p class="display-1">{{title}}</p>
{{prefix}} is Representing Themselves Skip This Step.
<div id="searchResults" class="hidden" style="margin-top:5px;">
<a id="searchResultsText" class="SkipStepStyle"></a>
<a
id="viewSearchResults"
style="font-weight: bold;"
class="hidden SkipStepStyle"
v-on:click="displayAgents"
>
View
</a>
</div>
<form class="mt-2 BuyerSellerAgentInfo">
<div class="form-row">
<div class="form-group col-md-6">
<input
ref="NameFirst"
type="text"
:name="prefix+'sAgent_NameFirst'"
placeholder="FIRST NAME"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_NameFirst'].Answer"
>
</div>
<div class="form-group col-md-6">
<input
ref="NameLast"
type="text"
:name="prefix+'sAgent_NameLast'"
placeholder="LAST NAME"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_NameLast'].Answer"
>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<input
ref="Email"
type="text"
:name="prefix+'sAgent_Email'"
placeholder="EMAIL ADDRESS"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_Email'].Answer"
>
</div>
<div class="form-group col-md-6">
<input
ref="Phone"
type="text"
:name="prefix+'sAgent_Phone'"
maxlength="14"
placeholder="PHONE #"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_Phone'].Answer"
>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<input
ref="Brokerage"
type="text"
:name="prefix+'sAgent_Brokerage'"
placeholder="AGENT'S BROKERAGE"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_Brokerage'].Answer"
>
</div>
<div class="form-group col-md-6">
<input
ref="License"
type="text"
:name="prefix+'sAgent_License'"
placeholder="AGENT'S LICENSE #"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_License'].Answer"
>
</div>
</div>
<input
class="AnswerChoice"
type="hidden"
:name="prefix+'sAgent_ID'"
:value="currentAnswers[prefix+'sAgent_ID'].Answer || '1'"
>
<input
class="AnswerChoice"
type="hidden"
:name="prefix+'sAgent_BrokerageID'"
:value="currentAnswers[prefix+'sAgent_BrokerageID'].Answer || '1'"
>
</form>
</center>
<div v-if="agents.length > 0" class="modal" id="AgentPopup">
<div class="vertical-alignment-helper">
<div class="modal-dialog vertical-align-center">
<div class="modal-content">
<div class="modal-body">
<center>
<h5 class="d-inline-block mb-3">Select {{prefix}}'s Agent:</h5>
</center>
<button v-on:click="displayCategories" type="button" class="close shadow" data-dismiss="modal">×</button>
<ul>
<li v-for="agent in agents">{{ agent.NameFull || agent.NameFirst+' '+agent.NameLast }}</li>
<li class="border-0">{{prefix}}’s agent is not in this list</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
Script:
import _ from 'lodash';
export default {
name: "AgentInformation",
props: {
friendlyIndex: {
type: String,
default: null,
},
title: {
type: String,
default: null,
},
answerChoices:{
type: Array,
default: () => []
},
currentAnswers: {
type: Object,
default: () => {},
},
prefix: {
type: String,
default: '',
},
token: {
type: String,
default: '',
},
},
methods: {
debounceFunction(func,timer){
let vueObject = this;
return _.debounce(()=>{
vueObject[func]();
},timer);
},
displayCategories(){
$('.categories').show();
},
displayAgents(){
$('.categories').hide();
$('#AgentPopup').modal({backdrop:'static',keyboard:false});
},
searchAgent() {
let vueObject = this;
console.log('calling searchAgent()');
let agentSearchRoute = correctVuexRouteURL(vueObject.$store.getters.routeName('search.agent'));
if (!agentSearchRoute) genericError('Agent Search Route Not Found. Error code: a-s-001');
else
{
let dataObject = {
NameFirst: this.$refs.NameFirst.value,
NameLast: this.$refs.NameLast.value,
Email: this.$refs.Email.value,
Phone: this.$refs.Phone.value,
License: this.$refs.License.value,
_token: this.token,
};
console.log(dataObject);
vueObject.$http.post(agentSearchRoute, dataObject).then((r) => {
let status = r.body.status;
if (status == 'success')
{
vueObject.agents = r.body.agents;
let searchResultsContainer = $('#searchResults');
let searchResultsText = $('#searchResultsText');
let viewSearchResultsLink = $('#viewSearchResults');
let agentCount =
vueObject.agents.length;
searchResultsContainer.removeClass('hidden');
if(agentCount > 0)
{
let rText = agentCount > 1 ? 'records' :
'record';
searchResultsText.text(agentCount+' '+rText+'
found.');
viewSearchResultsLink.removeClass('hidden');
}
else
{
if (!viewSearchResultsLink.hasClass('hidden'))
viewSearchResultsLink.addClass('hidden');
searchResultsText.text('No records found.');
}
}
});
}
},
},
data(){
return {
agents: [],
}
},
mounted() {
let vueObject = this;
console.log('mounted');
$(document).ready(function(){
$('#phone').mask('(###)-###-####');
$('.AnswerChoice').on('input', () => {
let searchAgent =
vueObject.debounceFunction('searchAgent',500);
searchAgent();
});
});
}
}
It seems that the issue is the template does not like the 'agents' variable to be inside of it. When I remove the modal container or just the references to 'agents' it works as expected. If I change the variable name it does not solve the issue.
Any thoughts on the solution? Am I missing something blatantly obvious and stupid?!
Edit: Something I forgot to add, I don't think affects this in any way but it is worth mentioning. This component is rendered dynamically inside of the parent.
Rendering the component:
<component
v-for="(component,i) in selectedView"
:is="component['Component']"
v-bind="bindAttributes(component)"
:key="component.ID"
>
</component>
Changing agents will cause the whole template to be re-run. Not just the bits that mention agents, everything in that template will be updated.
When a user types into one of your <input> elements you aren't storing that value anywhere. You've got a :value to poke the value in but you aren't updating it when the value changes. The result will be that when Vue re-renders everything it will jump back to its original value.
You should be able to confirm this by setting the initial values within currentAnswers to be something other than empty. You should find that whenever agents changes it jumps back to those initial values.
The solution is just to ensure that your data is kept in sync with what the user types in. Typically this would be done using v-model but that's a bit tricky in this case because you're using a prop for the values and you shouldn't really be mutating a prop (one-way data flow). Instead you should use events to communicate the required changes up to whichever component owns that data.
Here is a simple test case to demonstrate the issue in isolation:
new Vue({
el: '#app',
data () {
return {
count: 0,
value: 'initial'
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<input :value="value">
<button #click="count++">Click count: {{ count }}</button>
</div>

Knockout why does my first form.submit work but not the others?

I have three views which posts a JSON-object to backend to generate a PDF. The first in the hierarchy works, not the two others. The code is practically identical, and uses the same method to programmatically add a hidden input-field with the JSON-data.
I cannot for the life of me figure out what the problem is.
First i thought Knockout was creating the problem since the form-elements are added in a foreach-loop, in a AJAX-success-event, but every view is identical in this respect.
I've tried all variations of renaming, also I've tried to create the form and input programatically, appending them to the body and submitting. Still only the first works.
WORKING VIEW:
<div data-bind="visible: rep.messages().length > 0">
<div data-bind="foreach: rep.messages">
<div id="error" data-bind="css: color">
<div><span data-bind="css: icon"></span></div>
<div><div data-bind="html: text"></div></div>
<div>
<form action="/createPDF.html" method="POST" id="individlonpdf" name="loneandringar" target="_blank">
<span onclick=""></span>
Skriv ut
</form>
</div>
</div>
</div>
</div>
NOT WORKING VIEW:
<div data-bind="visible: rem.newEmployeeMessages().length > 0" id="rem-msg-area">
<div data-bind="foreach: rem.newEmployeeMessages">
<div id="error" data-bind="css: color">
<div><span data-bind="css: icon"></span></div>
<div><div data-bind="html: text"></div></div>
<div>
<form action="/createPDF.html" method="POST" id="ateranstallpdf" name="avanmalan" target="_blank">
<span onclick=""></span>
Skriv ut
</form>
</div>
</div>
</div>
</div>
JAVASCRIPT
Each click event-method is then directed towards the same print-method
Not-working:
rem.print = function(){
mainPdfPrint("ateranstallpdf", data, "avanmalan");
}
Working:
rep.print = function () {
mainPdfPrint("individlonpdf", data, "loneandringar");
}
The main-method:
function mainPdfPrint(formid, datasource, name, stringified) {
formid = "#" + formid;
var url = $(formid).attr("action");
var data = stringified ? datasource : JSON.stringify(datasource);
var inp = $("<input>", { "value": data, "name": name, "type": "hidden" });
if ($(formid + " input:hidden").length) {
$(formid + " input:hidden").remove();
}
$(formid).append(inp);
IK.global.logAnalytics(url);
document.forms[name].submit();
}
Both working and non-working form-elements are part of the document.forms-collection as they should be.
Any suggestions would be greatly appreciated!
UPDATE
After suggestions here, I've tried a different approach:
<div class="col-xs-12" id="" data-bind="visible:messages().length > 0">
<div data-bind="foreach: messages">
<div class="col-xs-12" id="error" data-bind="css:color">
<div class="col-xs-12 msg-templates">
<div class="col-xs-1 no-pad-l info-sign">
<span class="glyphicon" data-bind="css:icon"></span>
</div>
<div class="col-xs-9 no-pad-l">
<div class="col-xs-12 no-pad-lr msg" data-bind="html:text"></div>
</div>
<div class="col-xs-2 no-pad-lr text-right print">
<form class="col-xs-12 no-pad-l" action="/foretag/individkort/skapaPdfAvanmalan.html" method="POST" id="avanmalanpdf" name="avanmalan" target="_blank">
<span class="glyphicon glyphicon-print" onclick=""></span>
<input type="hidden" name="avanmalan" data-bind="value:ko.toJSON($root.printableEmployee)" />
Skriv ut
</form>
</div>
</div>
</div>
</div>
</div>
The problem still persists however, but setting a delay in the print-method triggers the submit-event:
self.print = function () {
setTimeout(function () {
document.forms.avanmalan.submit();
}, 600);
}
Your approach indicates that you seem to be not really familiar with knockout and try to do too much in a "jQuery" kind of way.
With knockout, your user interface is a function of your view model. All changes in the DOM have to be a result of changes in the underlying data. The task of manually adding input fields with JS code is not part of this approach.
In the following, there is no custom code that modifies the DOM. Everything is done by knockout.
var vm = {
rep: {
messages: [
{ color: '', icon: '', text: '<b>Message 1</b> - Text', data: 'some data here 1' },
{ color: '', icon: '', text: '<b>Message 2</b> - Text', data: 'some data here 2' },
{ color: '', icon: '', text: '<b>Message 3</b> - Text', data: 'some data here 3' }
]
},
catchPost: function () {
// dummy function to prevent an actual form POST
console.log("...would send the following data to the server:");
console.log(ko.toJSON(this.data, null, 2));
return false;
}
};
ko.applyBindings(vm);
button.submit {
border: 0px none;
padding: 0;
margin: 0;
text-decoration: underline;
background-color: transparent;
color: blue;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="with: rep">
<div data-bind="foreach: messages">
<div id="error" data-bind="css: color">
<div><span data-bind="css: icon"></span></div>
<div><div data-bind="html: text"></div></div>
<div>
<form action="/createPDF.html" method="POST" target="_blank" data-bind="submit: $root.catchPost">
<input type="hidden" name="loneandringar" data-bind="value: ko.toJSON(data)">
<button class="submit" id="print-tgl">Skriv ut</button>
</form>
</div>
</div>
</div>
</div>
There is no need for any custom code in such a simple scenario. Unless you have an explicit dependency on jQuery UI (or a similar library), I suggest you remove jQuery from your application altogether. You can use a dedicated Ajax library if you require it - such as reqwest - but generally speaking, jQuery is more of a hindrance than a help in knockout applications.

Declare reactive data properties in vue.js?

I have a very basic vue.js app:
var app = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
messages: [
'Hello from Vue!',
'Other line...'
]
}
})
The following html works fine:
<div class="container" id="app">
<div class="row" style="">
<div class="col-md-8 offset-md-2">
<span v-for="msg in messages">${msg}</span>
</div>
</div>
</div>
However very similar html block does not:
<div class="container" id="app">
<div class="row" style="">
<div class="col-md-8 offset-md-2">
<textarea id="chat_area" readonly="" rows="20">
<span v-for="msg in messages">${msg}</span>
</textarea>
</div>
</div>
</div>
[Vue warn]: Property or method "msg" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
I'm using Vue v2.3.3. What could be the problem?
As documentation says, interpolation in textareas won't work, so you need to use v-model.
If the only thing you want to do is to display html inside textarea, you could in theory use an ugly workaround by wrapping your array fields inside a function and set that function as textarea v-model:
var app = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
messages: [
'Hello from Vue!',
'Other line...'
]
},
computed:{
multiLineMessages: function(){
var result = "";
for(var message of this.messages){
result += '<span>' + message + '</span>'
}
return result;
}
}
});
template part:
<div class="container" id="app">
<div class="row" style="">
<div class="col-md-8 offset-md-2">
<textarea v-model="multiLineMessages" placeholder="add multiple lines">
</textarea>
</div>
</div>
</div>
It's more like a proof that it's doable but I highly don't recommend using it anywhere, as html shouldn't be generated this way (especially larger chunks of it).
jsFiddle preview

Search data on click event of button using smart table

I am very new to the smart table. I have gone through its documentation on Smart Table.
But the I haven't found how to bind data on click event in smart table?
Code is very big but I am trying to post it here.
<div class="table-scroll-x" st-table="backlinksData" st-safe-src="backlinks" st-set-filter="myStrictFilter">
<div class="crawlhealthshowcontent">
<div class="crawlhealthshowcontent-right">
<input type="text" class="crserachinput" placeholder="My URL" st-search="{{TargetUrl}}" />
<a class="bluebtn">Search</a>
</div>
<div class="clearfix"></div>
</div>
<br />
<div class="table-header clearfix">
<div class="row">
<div class="col-sm-6_5">
<div st-sort="SourceUrl" st-skip-natural="true">
Page URL
</div>
</div>
<div class="col-sm-2">
<div st-sort="SourceAnchor" st-skip-natural="true">
Anchor Text
</div>
</div>
<div class="col-sm-1">
<div st-sort="ExternalLinksCount" st-skip-natural="true">
External<br />Links
</div>
</div>
<div class="col-sm-1">
<div st-sort="InternalLinksCount" st-skip-natural="true">
Internal<br />Links
</div>
</div>
<div class="col-sm-1">
<div st-sort="IsFollow" st-skip-natural="true">
Type
</div>
</div>
</div>
</div>
<div class="table-body clearfix">
<div class="row" ng-repeat="backlink in backlinksData" ng-if="backlinks.length > 0">
<div class="col-sm-6_5">
<div class="pos-rel">
<span class="display-inline wrapWord" tool-tip="{{ backlink.SourceUrl }}"><b>Backlink source:</b> <a target="_blank" href="{{backlink.SourceUrl}}">{{ backlink.SourceUrl }}</a></span><br />
<span class="display-inline wrapWord" tool-tip="{{ backlink.SourceTitle }}"><b>Link description:</b> {{ backlink.SourceTitle }}</span> <br />
<span class="display-inline wrapWord" tool-tip="{{ backlink.TargetUrl }}"><b>My URL:</b> <a target="_blank" href="{{backlink.TargetUrl}}">{{ backlink.TargetUrl }}</a></span><br />
</div>
</div>
<div class="col-sm-2">
<div class="pos-rel">
{{ backlink.SourceAnchor }}
</div>
</div>
<div class="col-sm-1">
<div>
{{ backlink.ExternalLinksCount }}
</div>
</div>
<div class="col-sm-1">
<div>
{{ backlink.InternalLinksCount }}
</div>
</div>
<div class="col-sm-1">
<div ng-if="!backlink.IsFollow">
No Follow
</div>
</div>
</div>
<div class="row" ng-if="backlinks.length == 0">
No backlinks exists for selected location.
</div>
</div>
<div class="pos-rel" st-pagination="" st-displayed-pages="10" st-template="Home/PaginationCustom"></div>
</div>
and my js code is here.
module.controller('backlinksController', [
'$scope','$filter', 'mcatSharedDataService', 'globalVariables', 'backlinksService',
function ($scope,$filter, mcatSharedDataService, globalVariables, backlinksService) {
$scope.dataExistsValues = globalVariables.dataExistsValues;
var initialize = function () {
$scope.backlinks = undefined;
$scope.sortOrderAsc = true;
$scope.sortColumnIndex = 0;
};
initialize();
$scope.itemsByPage = 5;
var updateTableStartPage = function () {
// clear table before loading
$scope.backlinks = [];
// end clear table before loading
updateTableData();
};
var updateTableData = function () {
var property = mcatSharedDataService.PropertyDetails();
if (property == undefined || property.Primary == null || property.Primary == undefined || property.Primary.PropertyId <= 0) {
return;
}
var params = {
PropertyId: property.Primary.PropertyId
};
var backLinksDataPromise = backlinksService.getBackLinksData($scope, params);
$scope.Loading = backLinksDataPromise;
};
mcatSharedDataService.subscribeCustomerLocationsChanged($scope, updateTableStartPage);
}
]);
module.filter('myStrictFilter', function ($filter) {
return function (input, predicate) {
return $filter('filter')(input, predicate, true);
}
});
But It is working fine with the direct search on textbox.
but according to the requirement I have to perform it on button click.
Your suggestions and help would be appreciated.
Thanks in advance.
You can search for a specific row by making some simple tweaks.
add a filter to the ng-repeat, and filter it by a model that you will insert on the button click, like so: <tr ng-repeat="row in rowCollection | filter: searchQuery">
in your view, add that model (using ng-model) to an input tag and define it in your controller
then pass the value to the filter when you click the search button
here's a plunk that demonstrates this
you can use filter:searchQuery:true for strict search
EDIT:
OK, so OP's big problem was that the filtered values wouldn't show properly when paginated, the filter query is taken from an input box rather then using the de-facto st-search plug-in, So I referred to an already existing issue in github (similar), I've pulled out this plunk and modified it slightly to fit the questioned use case.

Categories