I have select list in my html binded to observable array
<select data-bind="options: myData, optionsText: 'Name', value: selectedId"></select>
When i load my page i set value to observable selectedId
But when i set value it immediately calls its subscribe event
selectedId.subscribe(function (row) {
// some logic for retrieving data
});
I dont want subscribe event to call when value is being set programatically but only want to call when user selects something from list.
Is it possible to do? Cant find any good examples.
Update1
i set value to my observable selectedId in this way
selectedId(ko.utils.arrayFirst(data(), function(item) {
return item.id=== 5;
}));
I think because of this its firing subscribe event
If you set the initial value before you actually subscribe, then you shouldn't get a subscription event firing. For example:
function ViewModel() {
var self = this;
self.selectedID = ko.observable();
self.selectedID(10); // this won't fire the event because we haven't subscribed yet
self.selectedID.subscribe(function(value) {
alert("value changed by user");
});
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="number" data-bind="value: selectedID" />
Now if you want to solve the general case, where the value might change programmatically later and you don't want the subscribe to fire then, you might want to have a flag that you can use to simply skip the processing of the subscribe handler. Something like:
function ViewModel() {
var self = this;
self.selectedID = ko.observable();
self.selectedID(10); // this won't fire the event because we haven't subscribed yet
var _skipNotification = false; // this tells us whether or not we should process subscribe
// putting this logic in a function makes it easier if you have multiple places
// where you need to programmatically set the id.
function setSelectedID(value) {
_skipNotification = true;
self.selectedID(value);
_skipNotification = false;
}
self.selectedID.subscribe(function(value) {
if (!_skipNotification) {
alert("value changed by user");
}
});
self.changeProgrammatically = function() {
setSelectedID(1);
};
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="number" data-bind="value: selectedID" />
<input type="button" data-bind="click: changeProgrammatically" value="change programmatically" />
Here's your fiddle fixed to use the technique above. A local variable _skipNotification is defined in the view model and checked in the subscribe event. Then the importData function that is called from the mouse click looks like this:
this.importData = function () {
skipNotification = true;
self.selectedGroup(ko.utils.arrayFirst(self.availableGroups(), function (val) {
//debugger;
return val.GroupId == 8;
}));
skipNotification = false;
console.log("Lets see if it hits me first");
};
This will set the selectedGroup without causing the body of the selectedGroup.subscribe to execute. Note that as a result the selectedGroupId doesn't get set so your one span will still say You have chosen Nothing despite the fact that something is selected in the drop-down. I'm not sure if that was what you were actually going for, but it seems pretty misleading. Especially since the only way to now get it to correctly read You have chosen Football Team is to select something else first and then reselect Football Team.
Related
I am trying to trigger a function which hides or show the images on the basis of data i have written two function one which calls the api which is in created hook and second function which renders the image . The problem is how do i call that second function after the dom is loaded , right now when i am trying to call in the first function or created it is returning me error that css cannot be changed of null.I have tried using mounted function with newtick but its still firing the render_badges function first and hence values are null inside
created:function(){
this.loadlike()
},
methods:{
loadlike:function(){
var self = this
this.$http.get('/api/user_profile').then(function (res) {
self.tasksdata = res.body
self.badges = self.tasksdata.data2
console.log(self.badges)
console.log(this.tasksdata)
console.log(this.max)
})
},
getHumanDate : function (date) {
return moment(date, 'YYYY-MM-DD hh-mm-ss').locale("en-gb").format('LL');
},
render_badges:function(){
var self = this
var counter = 0;
self.badges.map(function(e){
counter ++;
console.log(counter)
if(counter <=self.max){
document.getElementById("i").style.display = "initial";
}
else{
document.getElementById("i").style.display = "none";
}
})
},
mounted: function () {
this.$nextTick(function () {
this.render_badges();
})
}
Rather than manipulating the DOM, you should use v-if on the element itself that turns it on or off based on data. It is a different way of thinking than direct DOM manipulation learned in the jQuery days. Read more in the Vue docs about conditional rendering.
If you are wanting to trigger once the DOM is available, use mounted()
I have an HTML5/Bootstrap form with hidden fields:
style="display: none"
which i show/hide via jQuery:
show() | hide()
For field validation i use the attribute required.
I want to have all my hidden fields as required but when some of them don't appear then the form can't proceed to submission.
Any thoughts about how can i have validation enabled only to fields displayed by user selections?
You can use this trick:
inside HTML form:
<input type="text" name="username" required="required" class="input-hidden">
CSS class:
.input-hidden{
height:0;
width:0;
visibility: hidden;
padding:0;
margin:0;
float:right;
}
You can add a class name for all the required attributes in the html:
<input type="text" name="first_name" class="card-payment-info" required>
<input type="text" name="last_name" class="card-payment-info" required>
and, from js event like a click, you can enable or disable the required attributes:
// disable require:
$(".card-payment-info").attr('required', false);
// enable require
$(".card-payment-info").attr('required', true);
Add a class .hideable to your hideable required inputs.
Then use these functions instead of your show() and hide():
function show1() {
//Your show() code here
$('.hideable').attr('required', 'required');
}
function hide1() {
//Your hide() code here
$('.hideable').removeAttr('required');
}
1 - Change the form to "novalidate"
2 - Catch the submit event
3 - Force the browser to check individually each visible input with input.reportValidity()
$('form')
.attr('novalidate', true)
.on('submit', function(){
var isValid = true;
$('input:visible,select:visible,textarea:visible', this).each(function() {
// report validity returns validity of input and display error tooltip if needed
isValid = isValid && this.reportValidity();
// break each loop if not valid
return isValid;
});
// do not submit if not valid
return isValid;
})
I've made a JQuery tool for this that also uses a mutation observer to automatically apply on dynamically created forms.
https://github.com/severinmoussel/VisibilityFormValidator/blob/master/VisibilityFormValidator.js
I wrote a drop-in replacement for the built-in show() and hide() that removes required on hide and restores them back on show (for all child elements).
(function ($) {
var oldShow = $.fn.show;
$.fn.show = function () {
oldShow.apply(this, arguments); //run the original "show" method
this.find("[notrequired]").prop("required", true).removeAttr("notrequired");
return this;
};
var oldHide = $.fn.hide;
$.fn.hide = function () {
oldHide.apply(this, arguments); //run the original "hide" method
this.find("[required]").prop("required", false).attr("notrequired", "1");
return this;
};
})(jQuery);
UPD: published as a gist here in case anyone wants to add anything https://github.com/alex-jitbit/jquery-required-visibility/blob/main/showhide.js
You can add pointer-events: none;, so the user can't click on the hidden element and also the cursor doesn't change when you hover it. As #DeadApe answered in this question
https://stackoverflow.com/a/65356856/6938902
One solution is to write your own validation-function:
This function checks all textareas and inputs in the default way, but first check if the input is displayed.
function validateFormOnlyVisableInputs(elForm) {
var inputs = elForm.querySelectorAll("input, textarea");
for (var i = 0; i < inputs.length; i++) {
console.log(i);
// display:none inputs ignorieren
if (inputs[i].offsetParent === null) continue;
if (!inputs[i].checkValidity()){
inputs[i].reportValidity();
return false;
}
}
return true;
}
You have to add an eventlistener to the submit-button and call the valdation function:
// this is out of a class, so you have to remove this...
// if you have problems you can write me ;)
this.elSendButton = this.elForm.querySelector("Button");
this.elSendButton.addEventListener("click", async (e) => {
e.preventDefault();
if (validateFormOnlyVisableInputs(this.elForm)) {
this.elForm.submit();
...
So I'm basically developing an automated click-paste-and-upload system for mutiple texts and files inside a google page.
This method helped me get the instances of objects that I'm looking for: buttons, textboxes, richtextboxes, etc.
Now I want to work with them.
So for example I know the id of a button , and the function subscribed to its click event. How do I trigger the click event from the extension ? I've tried injecting a script with the click event handler (discovered with DOM inspector) at "document_startup" but I don't get an error or anything else.
Here's the content script! The loggerhead function should have inserted the script but I don't think it did. What might be the reason for the blow code not giving anything?
// Runs a function for every added DOM element that matches a filter
// filter -- either function(DOM_node){/*...*/}, returns true or false
// OR a jQuery selector
// callback -- function(DOM_node){/*...*/}
function watchNodes(filter, callback){
observer = new MutationObserver( function (mutations) {
mutations.forEach( function (mutation){
if(typeof filter === "function"){
$(mutation.addedNodes).filter(
function(i){ return filter(this); }
).each(
function(i){ callback(this); }
);
} else {
$(mutation.addedNodes).filter(filter).each(
function(i){ callback(this); }
);
}
});
});
// For every added element, a mutation will be processed
// with mutation.taget == parent
// and mutation.addedNodes containing the added element
observer.observe(document, { subtree: true, childList: true });
}
function loggerhead(node) {
console.log("passhead");
//also inject jquery
var jqueryEl = document.createElement('script');
jqueryEl.setAttribute('src', chrome.extension.getURL('jquery-1.11.1.min.js'));
jqueryEl.setAttribute('type', 'text/javascript');
var scriptEl = document.createElement('script');
scriptEl.setAttribute('src', chrome.extension.getURL('script.js'));
scriptEl.setAttribute('type', 'text/javascript');
node.appendChild(jqueryEl);
node.appendChild(scriptEl);
}
watchNodes("head", loggerhead);
// method not working
//var gmailHead = jQuery("head", document).get(0);
script.js contains the function of subscribed to the click event of the button that I've managed to find through the DOM inspector:
function Cdb(b){return function(){if(Vbb()){return Ddb(b,this,arguments)}else{var a=Ddb(b,this,arguments);a!=null&&(a=a.val);return a}}}
You should try to call the existing click handler like
buttonElement.click()
I'm using the following code to load all Json data.
$.getJSON("/Home/GetSortedLists", function (allData) {
var mappedSortedLists = $.map(allData, function (item) { return new SortedLists(item) });
viewModel.sortedlists(mappedSortedLists);
});
I also need to load a single record from the same Json data; the record with the highest SortedListsID value (i.e. the last record entered).
Can anybody suggest the best way to do this? I've considered adding viewModel.lastsortedlist and amending the above code somehow. I've also considered creating a last custom binding to do something like:
<tbody data-bind="last: sortedlists.SortedListID">
All advice welcome.
Unless you want to do more ui-related stuff with the record, I don't think you need the custom binding.
It should be enough to compute it in the getJSON callback and save it in the viewModel:
$.getJSON("/Home/GetSortedLists", function (allData) {
var mappedSortedLists = $.map(allData, function (item) { return new SortedLists(item) });
viewModel.sortedlists(mappedSortedLists);
//correct the sort function if it's bad, or drop it if allData is already sorted
var sortedData = allData.sort(function(a,b){ return a.SortedListID - b.SortedListID})
viewModel.lastSortedList(sortedData[sortedData.length - 1])
});
Or, if it can change outside the getJSON callback, you could also make it a computed observable:
viewModel.lastSortedList = ko.computed(function(){
//correct the sort function if it's bad, or drop it
var sortedData = mappedSortedLists().sort(function(a,b){ return a.SortedListID - b.SortedListID})
return sortedData[sortedData.length - 1]
}, this)
I have a text input element that I want user to enter city or zip code and I want to trigger a javascript function as the user types text. My html is as follows
<input type="text"
placeholder="Zip Code Or City Name"
class="input-block-level"
data-bind="value: searchterm, valueUpdate: 'afterkeydown', event: { keyup: getZipCodes }" />
in my viewmodel I have
var getZipCodes = function () {
if (searchterm().length < 2) {
return;
}
datacontext.getZips(searchterm(), zipcodes);
return true;
};
But it turns out that the getZipCodes is never invoked.
I tried data-binding to "onkeyup" but no go, I am sure there is some subtle error I am making. Can someone help me find what I am doing wrong?
I tried changing getZipCodes to getZipCodes = function(data, event) {...} but that does not work either. The function just does not get invoked.
By the way I am trying to do pretty much same as Knockout event binding for input keypress causes weird behavior
but does not work for me.
After changing the definition of getZipCodes as follows it started working.
function getZipCodes(data, event) {
if (searchterm().length < 2) {
return true;
}
datacontext.getZips(searchterm(), zipcodes);
return true;
};
Instead of
var getZipCodes = function()
if I use
function getZipCodes()....
it works, not really sure what the difference is.