script only executed once - javascript

I have created a web component and used in two different places as follows.
<div class="sub_container">
<label>Users in Debt</label>
<input placeholder="Search by user name" class="input_style">
<my-component filter="userdebt"></my-component>
</div>
<div class="sub_container">
<label>Debts</label>
<input placeholder="Search by debt description" class="input_style">
<my-component filter="debt"></my-component>
</div>
</div>
As you can see i have used <my-component filter="userdebt"></my-component> in 2 places.
Here i'll show my web component
connectedCallback() {
console.log("ABC");
window.globalVar = this.getAttribute("filter");
this.innerHTML = `
<html>
<body>
<div id="templates">
<template id="book-template">
<li><span class="title"></span> — <span class="author"></span></li>
</template>
<ul id="books"></ul>
</div>
<script type="module" src="./js/testingjs.js"></script>
</body>
</html>
`;
var yourElement = document.getElementById('templates');
yourElement.id = globalVar;
console.log("MyComp ", yourElement.id);
}
customElements.define('my-component', MyComponent);
But even though i have used my-component twice in the html i can see the console.log only once meaning it's executing only once. What can be the reason for this?

A Javascript ES6 module is only loaded the first time it's needed.
Therefore its content is only executed once (when it is loaded).
You should call explicitly a function defined in the Javascript module in connectedCallback() method if you want it to be exectuted for each custom element created.

Related

Can't re-render html in vee-validate component

Vue.js v2.6.11 / vee-validate v3.2.2
I have a button that will push new element to form.demand (data in vue app) on click event.
And if form.demand update, html in v-for should be updated.
After I wrap it in vee-validate component , it not works.
form.demand will update, but v-for won't.
I try to put same html in test-component, when form.demand update, v-for update too.
I can't figure out why...
following is my code:
HTML
<div id="content">
<test-component>
<div v-for="demand in form.demand">{{demand}}</div>
</test-component>
<validation-provider rule="" v-slot="v">
<div #click="addDemand">new</div>
<div v-for="(demand,index) in form.demand">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
</validation-provider>
</div>
javascript
Vue.component('validation-provider', VeeValidate.ValidationProvider);
Vue.component('validation-observer', VeeValidate.ValidationObserver);
Vue.component('test-component',{
template: `
<div>
<slot></slot>
</div>
`
})
var app = new Vue({
el: "#content",
data: {
form: {
demand: [],
},
},
methods: {
addDemand(){
this.form.demand.push({
name : "demand name",
count: 1
})
}
})
------------Try to use computed & Add :key----------------
It's still not work. I get same result after this change.
HTML
<validation-provider rule="" v-slot="v">
<div #click="addDemand">new</div>
<div v-for="(demand,index) in computed_demand" :key="index">
<!--.........omitted.........-->
</validation-provider>
Javascript
var app = new Vue({
el: "#content",
// .......omitted
computed:{
computed_demand() {
return this.form.demand;
}
},
})
I think I found the problem : import Vue from two different source. In HTML, I import Vue from cdn. And import vee-validate like following:
vee-validate.esm.js
import Vue from './vue.esm.browser.min.js';
/*omitted*/
validator.js
import * as VeeValidate from './vee-validate.esm.js';
export { veeValidate };
main.js
// I didn't import Vue from vue in this file
import { veeValidate as VeeValidate } from './validator.js';
Vue.component('validation-provider', VeeValidate.ValidationProvider);
HTML
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- at end of body -->
<script src="/static/javascripts/main.js" type="module"></script>
</body>
After I fix this( import vee-validate from cdn, or import Vue by ES6 module).
It works, although it still have infinite loop issue with vee-validate.
Sorry for I didn't notice that import vue from two different source.
Please provide a key in you v-for. see code below
<div v-for="(demand,index) in form.demand" :key="index">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
Or, make a computed property that will hold your form.demands array, like this one
computed: {
form_demands: function() {
return this.form.demand
}
}
then call this computed property in your v-for
<div v-for="(demand,index) in form_demands" :key="index">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
Or, use the vue forceUpdate method
import Vue from 'vue';
Vue.forceUpdate();
Then in your component, just call the method after you add demand
this.$forceUpdate();
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.

How to manage html logic in a separate file

i have a piece of html code that in charge of presenting a list based on certain conditions:
<!-- Show list only if there are more than 5 results -->
<div list.numberOfResults > 10">
<b>Name: </b>{{list.name}} <b>ID: </b>{{list.id}}
<b>Country: </b>{{list.country}}
</div>
<!-- Show list only if there are less than 10 results -->
<div list.numberOfResults < 10">
<b>Name: </b>{{list.name}} <b>ID: </b>{{list.id}}
<b>Country: </b>{{list.country}}
</div>
Now, I also have some optional parameter (list.country) so I need to check if its not empty before as well.
I believe there is a way to take this logic outside of this html file and make a file that is responsable of the logic and the html will present the data accordingly, can someone please share a simple example of how this can be done based on my code?
thanks!!
Since There are two component you can keep them in a separate file like name
component.html
Then you can import in index.html like
<link rel="import" href="component.html" >
Or you can just grab specific portion from the file like
...
<script>
var link = document.querySelector('link[rel="import"]');
var content = link.import;
// Grab DOM from component.html's document.
var el = content.querySelector('.component');
document.body.appendChild(el.cloneNode(true));
</script>
</body>
DEMO :https://plnkr.co/edit/6atoS1WOK5RzKSmNeR8n?p=preview
You can use ngSwitch when you want to show HTML pieces conditionally,
#Component({
selector: 'my-app',
template: `
<div [ngSwitch]="list.numberOfResults>10"> // here
<div *ngSwitchCase ="true"> // here
<b> Template1 </b><br>
<b>Name: </b>{{list.name}} <br>
<b>ID: </b>{{list.id}} <br>
<b>Country: </b>{{list.country}}
</div>
<div *ngSwitchCase ="false"> // here
<b> Template2 </b><br>
<b>Name: </b>{{list.name}} <br>
<b>ID: </b>{{list.id}} <br>
<b>Country: </b>{{list.country}}
</div>
<div>
`,
})
export class App {
list={numberOfResults:2,name:'myList',id:1,country:'USA'};
}

Redirecting to specific slug from textfield in ember

I am totally new to ember so please be nice :)
I have an Ember app where i want to redirect to a specific slug taken from an textfield input. In my .hbs i have the following code:
<div class="liquid-container">
<div class="liquid-child">
<div class="desktop-layout-scroll-container">
<div class="overlay-info-layout">
<div class="overlay-info-layout-content">
<h1 class="expired-overlay-status">{{t 'code.title'}}</h1>
<h2 class="expired-overlay-explanation">
{{t 'code.description'}}<br>
</h2>
<div class="row">
<div class="col-xs-offset-3 col-xs-6">
<input name="txtSlug" type="text" id="txtSlug" class="field" />
<input type="submit" name="btnGo" value="" id="btnGo" class="btn" onclick="javascript:SubmitForm()" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
function SubmitForm(){
var Slugtxt = document.getElementById("txtSlug").value;
window.location = "http://www.google.com/" + Slugtxt;
}
</script>
You should never embed JavaScript in an Ember .hbs file. This code should really go in your controller for this class. First, generate your controller:
ember g controller <name_of_route>
Then inside your controller, you want to define two things. The slugtxt variable, and the redirecting as an action. That would look something like this:
import Ember from 'ember';
export default Ember.Controller.extend({
slugtxt: '',
actions: {
redirect() {
window.location = "http://www.google.com/" + this.get('slugtxt');
}
}
}
Then, back in your template, you would want to change your input to be mapped to the slugtxt variable from your controller, and set the action of the button to the redirect action that you defined in your controller. That would look something like this:
{{input value=slugtxt}}
<button {{action "redirect"}}>Submit</button>
No need to worry about using the <input> tag, you generally wont be using form data with ember.

DOM Scripting in Google Polymer

I just worked through the Google Polymer tutorial and I am building my first own element. And I am missing some DOM-Scripting Functions I know from Prototype and jQuery that made my life very easy. But maybe my methods are just not right. This is what I have done so far:
<polymer-element name="search-field">
<template>
<div id="searchField">
<ul id="searchCategories">
<li><a id="search-categories-text" data-target="text" on-click="{{categoryClick}}">Text</a></li>
<li><a id="search-categories-videos" data-target="videos" on-click="{{categoryClick}}">Videos</a></li>
<li><a id="search-categories-audio" data-target="audio" on-click="{{categoryClick}}">Audio</a></li>
</ul>
<div id="searchContainer">
<input id="searchText" type="text" />
<input id="searchVideos" type="text" />
<input id="searchAudio" type="text" />
</div>
</div>
</template>
<script>
Polymer({
ready: function() {
},
categoryClick: function(event, detail, sender) {
console.log(sender.dataset.target);
console.log(this.$.searchField.querySelector('#searchContainer input'));
this.this.$.searchField.querySelector('#searchContainer input');
}
});
</script>
</polymer-element>
What I want to do is to set an active class to the bottom input-fields when one of the above links are clicked. On jQuery I would just observe a link and deactivate all input fields and activate the one input field I want to have. But I am not sure how to do it without jQuery. I could just use all the native javascript functions with loops etc but is there anything polymer can offer to make things easier?
Does this example do what you want?
<script src="//cdnjs.cloudflare.com/ajax/libs/polymer/0.3.3/platform.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/polymer/0.3.3/polymer.js"></script>
<polymer-element name="search-field">
<template>
<style>
.hideMe {
display: none;
}
</style>
<div id="searchField">
<ul id="searchCategories">
<template repeat="{{category in searchCatergories}}">
<li><a on-click="{{categoryClick}}">{{category}}</a></li>
</template>
</ul>
<div id="searchContainer">
<template repeat="{{category in searchCatergories}}">
<div class="{{ { hideMe: category !== selectedCategory} | tokenList }}">
<label>Search for {{category}}</label>
<input id="search{{category}}" type="text">
</div>
</template>
</div>
</div>
</template>
<script>
Polymer({
searchCatergories: [
"Text",
"Video",
"Audio"
],
selectedCategory: 'Text',
categoryClick: function(event, detail, sender) {
// grab the "category" item from scope's model
var category = sender.templateInstance.model.category;
// update the selected category
this.selectedCategory = category;
// category
console.log("category", category);
// you can also access the list of registered element id's via this.$
// try Object.keys(this.$) to see registered element id's
// this will get the currently showing input ctrl
selectedInputCtrl = this.$["search" + category];
}
});
</script>
</polymer-element>
<search-field></search-field>
I've created an array for the categories and added two repeat templates.
I've setup a .hideMe class which is set on all input elements that aren't the currently selected category.
Info on dynamic classes - https://www.polymer-project.org/docs/polymer/expressions.html#tokenlist
Info on repeat - https://www.polymer-project.org/docs/polymer/binding-types.html#iterative-templates
Hope that helps

How can I apply KnockoutJS on a master page and an individual page?

I'm stuck on an older WebForms project and I'd like to know if there's a recommended approach for my scenario.
Goal
I have a feedback form in a modal dialog that I bound up using KnockoutJS.
I would like the feedback form to be available on all pages, via a link in the footer of the site.
I would like to have several other pages using knockout as well with their own individual scripts & bindings, irrespective of the feedback form bindings in the modal.
I have some pages that do not use knockout at all. I would like them not to have to insert code to accomplish this.
I would like to avoid global variables, if possible, in favor of namespaced JavaScript.
In essence, I would like for the viewmodels on the page and the feedback viewmodel not to be aware of each others' existence.
Current Setup
Our footer links are in a Site.master file, and so that's where I've placed the Feedback.js script and the div for the modal which has the bindings. So on the master page, I call ko.applyBindings(vm, referenceToFeedbackDiv), which works fine to wire up the feedback form.
Our individual pages occasionally have a knockout viewmodel, and so they may call ko.applyBindings(vm), since to their knowledge they'd like to apply the vm to their entire page.
Problem
This causes a conflict in knockout because one vm is being applied to the feedback form via the Site.master call, and one vm is being applied to the entire body by the page after it.
Question
How can I enable these two things -- a modal dialog across all pages that uses knockout, and individual knockout pages -- to work in harmony?
Demonstration of the Issue in (the Current) Code
Remember, the issue is that I want to be able to have one feedback VM that applies only to the feedback div across the client site, and I want to have other VMs able to be applied that aren't required to know anything about the feedback vm.
Master Page file (Site.Master) -- Excerpt
This is on every page:
<div class="page">
<div class="main">
<asp:ContentPlaceHolder ID="MainContent" runat="server" />
</div>
</div>
<div class="footer">
© <%=DateTime.Now.Year.ToString() %> Company, Inc. | Home | About |
<!-- begin feedback area -->
<span id="FeedbackArea">
<a data-bind="click: showModal">Feedback</a>
<div id="feedback-modal" title="What's on your mind?">
<div class="btn-group" id="feedbackButtonGroup">
<button class="btn" data-bind="click: UpdateFeedbackType" style="padding-top: 6px;">
<i class="fa fa-warning fa-2x fa-align-center"></i>
<br />
<span>Problem</span>
</button>
<button class="btn" data-bind="click: UpdateFeedbackType" style="padding-top: 6px;">
<i class="fa fa-question-circle fa-2x fa-align-center"></i>
<br />
<span>Question</span>
</button>
<button class="btn" data-bind="click: UpdateFeedbackType" style="padding-top: 6px;">
<i class="fa fa-lightbulb-o fa-2x fa-align-center"></i>
<br />
<span>Suggestion</span>
</button>
<button class="btn" data-bind="click: UpdateFeedbackType" style="padding-top: 6px;">
<i class="fa fa-thumbs-o-up fa-2x fa-align-center"></i>
<br />
<span>Praise</span>
</button>
<button class="btn" data-bind="click: UpdateFeedbackType" style="padding-top: 6px;">
<i class="fa fa-info-circle fa-2x fa-align-center"></i>
<br />
<span>General</span>
</button>
</div>
<br />
<br />
<textarea rows="5" placeholder="Enter feedback here" data-bind="value: feedbackText, valueUpdate: 'afterkeydown'"></textarea>
<br />
<br />
<button>Send Feedback</button>
<button data-bind="click: CancelFeedback">Cancel</button>
<h3>Other Information: </h3>
<ul>
<li><strong>Feedback Type:</strong> <span data-bind="text: feedbackType"></span></li>
<li><strong>Current URL:</strong> <span data-bind="text: pageUserIsOn"></span></li>
<li><strong>Current User: </strong><%=hdnLoggedInUsername.Value %></li>
<li><strong>Current Client: </strong>[Not yet captured]</li>
<li><strong>Current Tab: </strong>[Not yet captured]</li>
</ul>
</div>
</span>
<!-- End feedback area -->
</div>
Feedback.JS -- This is also included in every page
...a somewhat-namespaced definition of a FeedbackVM:
var FeedbackNamespace = FeedbackNamespace || {};
..the definition of the namespace itself:
FeedbackNamespace = {
ViewModel: function () {
// etc. etc.
}
};
...and the declaration of a VM variable plus wiring it up on document.ready():
var FeedbackVM;
$(document).ready(function () {
FeedbackVM = new FeedbackNamespace.ViewModel();
ko.applyBindings(FeedbackVM, $('#FeedbackArea')[0]);
FeedbackVM.Start();
log('FeedbackVM started');
});
Other Pages without Knockout / JS
Other pages may or may not have any javascript on them at all, let alone knockout. On these pages, the FeedbackVM currently works fine.
Pages with their own Knockout ViewModel
These pages would have their own namespaced JS file with their own document.ready() event, that creates a vm of say invoiceUploaderVM = new InvoiceUploader.ViewModel(), and then calls ko.applyBindings(invoiceUploaderVM).
This is where we run into trouble.
Update: One potential Approach and a little trouble
In the Site.master page, I wrapped my entire footer in a "stopBindings: true" div:
<div data-bind="stopBindings: true">
<div class="footer" id="footerDiv">
<!-- Feedback Viewmodel stuff in here -->
</div>
</div>
I've defined stopBindings as:
ko.bindingHandlers.stopBindings = {
init: function () {
return { controlsDescendantBindings: true };
}
};
My Feedback.js file, loaded on every page as part of a global JS file, has:
var FeedbackNamespace = FeedbackNamespace || {};
FeedbackNamespace = {
// defines viewmodel, etc. etc.
};
var FeedbackVM;
$(document).ready(function () {
FeedbackVM = new FeedbackNamespace.ViewModel();
ko.applyBindings(FeedbackVM, $('#footerDiv')[0]);
FeedbackVM.Start();
log('FeedbackVM started');
});
This approach works perfectly well -- as long as there are no other viewmodels being bound. On the pages that inherit from my master page, I might have something like:
$(document).ready(function () {
'use strict';
vm = new invoiceUploader.ViewModel();
ko.applyBindings(vm);
});
I would expect that this:
Sets up the feedback viewmodel applied to the div, stopping other viewmodels
Sets up the invoiceUploader viewmodel and applies it to the body (which is then stopped by the stopBindings div)
However, instead I get an error upon loading the child page along the lines of:
Commenting the line to apply the feedback bindings makes this work just fine again.
What am I doing wrong?
I think I would put the view model for your modal in a global object and do whatever you need to do with it aside from applying the bindings in a shared script:
window.feedbackModal = {
foo: ko.observable("Whatever you need to do here"),
bar: ko.observable("assuming it can be done the same on every page")
};
Then in the Site.master
<div class="feedback-modal" data-bind="with: feedbackModal">
<p data-bind="text: foo"></p>
<p data-bind="text: bar"></p>
</div>
And in every individual page's script:
function ViewModel() {
this.individualProperty = ko.observable(true);
this.specificAction = function() { /* do something specific to this page */ };
this.feedbackModal = window.feedbackModal;
}
ko.applyBindings(new ViewModel());
So window.feedbackModal could be undefined and it won't cause you problems, but if you ko.applyBindings, you have to have a feedbackModal property exposed in the view model or you'll get errors applying those bindings.
Of course, there are more clever ways you could implement this basic idea in order to fit your patterns the best, but the big point is, as you know, you can't apply bindings twice, so you need to defer that task to your most specific code and expose your reusable code to to it.
Here is another strategy for separation of common modules from page dependant modules:
// An example of a module that runs on everypage
var modalDialog = function(){
this.name = "dialog1";
this.title = ko.observable("My Modal Title");
this.content = ko.observable("My Modal content is also something");
}
// An example of a module that runs on everypage
var modalDialog2 = function(){
this.name = "dialog2";
this.title = ko.observable("My Modal Title 2");
this.content = ko.observable("My Modal content is also something 2");
}
// Either generate it automatically or by hand
// to represent which modules are common
var commonModules = [modalDialog, modalDialog2];
// An example of a module only for this page
var pageModule = function(){
this.pageFunction = function(){
alert("Called page function");
}
}
// Composition is the final object you will actually bind to the page
var composition = {
pageMod: new pageModule()
}
// Let's add the common modules to the composition
ko.utils.arrayForEach(commonModules, function(item){
var module = new item();
composition[module.name] = module;
});
// Bind the composition
ko.applyBindings(composition);
example HTML for this would be:
<div class="modalDialog">
<h2 data-bind="text: dialog1.title"><h2>
<h2 data-bind="text: dialog1.content"><h2>
</div>
<div class="modalDialog">
<h2 data-bind="text: dialog2.title"><h2>
<h2 data-bind="text: dialog2.content"><h2>
</div>
<div id="content">
<h2>Welcome to page</h2>
<div id="somePageStuff">
Click me
</div>
</div>
Link to the jsfille for this
You can set this up by using a technique to not have scope your bindings to a specific area in your page.
Check out: How to stop knockout.js bindings evaluating on child elements
Example:
http://jsfiddle.net/anAgent/RfM2R/
HTML
<div id="Main">
<label data-bind="text: ViewModel.Name">default</label>
<div data-bind="stopBindings: true">
<div id="ChildBinding">
<label data-bind="text: AnotherViewModel.Name">default</label>
</div>
</div>
</div>
JavaScript
$(function () {
ko.bindingHandlers.stopBindings = {
init: function () {
return {
controlsDescendantBindings: true
};
}
};
var data = {
ViewModel: {
Name: "Testing"
}
};
var data2 = {
AnotherViewModel: {
Name: "More Testing"
}
};
ko.applyBindings(data, $("#Main")[0]);
ko.applyBindings(data2, $("#MyModalHtml")[0]);
});

Categories

Resources