Almost there, just need the popover to appear only after submit button is clicked and the field has an alert class added to it.
Note: Hover only works for isValid is true in the binding handler, otherwise testing for not true requires a click rather than a hover :S
http://jsfiddle.net/hotdiggity/UUb4M/
HTML:
<input data-bind="value: lastName, popover: isValidField" data-placement="below" data-title="Alert" data-content="We have identified this information is incorrect and must be updated."/>
JS:
self.lastName = ko.observable().extend({ required: true });
self.isValidField = ko.observable();
this.submit = function () {
if (self.errors().length === 0) {
alert('Thank you.');
} else {
self.errors.showAllMessages();
self.isValidField(self.lastName.isValid());
}
};
Binding:
ko.bindingHandlers.popover = {
init: function (element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor(),
valueUnwrap = ko.unwrap(value),
allBindings = allBindingsAccessor(),
isValid = allBindings.value;
if (isValid) {
$(element).popover({
trigger: "hover"
});
} else {
$(element).popover("hide");
}
},
Update: //See jsfiddle link above (the same code)
Here is a working version for you. The popover will appear only if the validation for the lastName field is not met and only when the submit button is clicked. It will go away if the user types something into the lastName field. See this updated fiddle
The update function of a binding handler creates dependencies on observables that it accesses, so essentially the update of a binding handler will be triggered when that observable is changed. In your case, I did it with the isValidField observable. See notes in the code
var viewModel = function () {
var self = this;
self.lastName = ko.observable().extend({ required: true });
self.isValidField = ko.observable();
// once the popover is shown, we want it to go away if the user types something
// into the lastName field. We do this by triggering the popover binding handler
// by changing the value of isValidField
self.lastName.subscribe(function(val){
if(val && val.length > 0){ self.isValidField(true); }
});
// need to create your errors object
self.errors = ko.validation.group(self);
this.submit = function () {
if (self.errors().length === 0) {
alert('Thank you.');
} else {
self.errors.showAllMessages();
// change the value of isValidField to trigger the popover binding handler's
// update
self.isValidField(self.lastName.isValid());
}
};
};
ko.bindingHandlers.popover = {
update: function (element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor(),
valueUnwrap = ko.unwrap(value);
if (valueUnwrap === false) {
$(element).popover('show');
} else {
$(element).popover("destroy");
}
}
};
Related
I am trying to create an event that fires some functions depending on the id of an input[type=radio]. If the Id clicked is different to maybe_evtDiag, it should call this.applySubConditionalRequired(); and this.bindUISubActions();. Why is my code not working?
var SubFormStuff = {
init: function()
this.applySubConditionalRequired();
this.bindUISubActions();
},
bindUISubActions: function() {
// when a radio or checkbox changes value, click or otherwise
$("input[type='radio'].stepThreeDiag").change(function() {
if($(this).attr("id") == "maybe_evtDiag") {
$(this).prop('checked', false);
}else{
//this is not working //
applySubConditionalRequired(this);
displaySubFormRequired(this);
}
});
},
applySubConditionalRequired: function() {
$(".require-if-subevent-active").each(function() {
var el = $(this);
// does something
});
},
displaySubFormRequired: function() {
$(".div-subevent-class").each(function() {
var el = $(this);
// does something else
});
}
};
SubFormStuff.init();
Like you did in the init(), add a reference to the object (this) to call a sibling function (not to lose the context):
bindUISubActions: function() {
var _SubFormStuff = this;
// when a radio or checkbox changes value, click or otherwise
$("input[type='radio'].stepThreeDiag").change(function() {
if($(this).attr("id") == "maybe_evtDiag") {
$(this).prop('checked', false);
} else{
_SubFormStuff.applySubConditionalRequired();
_SubFormStuff.displaySubFormRequired();
}
});
More details on scope and context in JavaScript
You should call the methods like this:
bindUISubActions: function() {
// Store the reference to the current object
var self = this;
// when a radio or checkbox changes value, click or otherwise
$("input[type='radio'].stepThreeDiag").change(function() {
if($(this).attr("id") == "maybe_evtDiag") {
$(this).prop('checked', false);
} else{
self.applySubConditionalRequired();
self.displaySubFormRequired();
}
});
}
This way you can assing to self the current scope, and use it later on any other function call in the same execution scope.
More about javascript scope
You are trying to call applySubConditionalRequired(this) and displaySubFormRequired(this) in the wrong context you should get applySubConditionalRequired and displaySubFormRequired are not defined.
Try this:
bindUISubActions: function() {
// when a radio or checkbox changes value, click or otherwise
var that = this;
$("input[type='radio'].stepThreeDiag").change(function() {
if($(this).attr("id") == "maybe_evtDiag") {
$(this).prop('checked', false);
}else{
//it should work now //
that.applySubConditionalRequired(this);
that.displaySubFormRequired(this);
}
});
},
i have a little problem with vars on event. So i have a plugin whitch called another plugin and i want to get a jquery element from called plugin to main plugin... like this.
(function($) {
$.fn.meRadioCheck = function(options, callback) {
options = $.extend($.fn.meRadioCheck.defaults, options);
var plugin = this;
plugin.init = function() {
return this.each(function() {
var $this = $(this);
var $span = $('<span/>');
var name = $this.attr('name');
/* here some stuff... */
/* check for Events */
if (($._data(this, "events") == null)) {
/* Change Event an Element binden */
$this.bind("xonChange", function() {
options.CheckBox_onChange.call($this)
});
$this.on({
change: function() {
/* here some stuff... */
/* throw Change Event */
$this.trigger("xonChange");
},
});
};
});
}
return this.each(function() {
var $this = $(this);
var name = $this.attr('name');
if (options.checked != $this.prop('checked')) { $this.click(); }
if (options.disabled) { $this.prop('disabled', true) } else { $this.prop('disabled', false) }
});
};
// Standard-Optionen für das Plugin
$.fn.meRadioCheck.defaults = {
'checked': false, /* This Checkbox or Radio Button is checked */
'debug': false, /* Debug Modus für das Plugin ein od. ausschalten */
'disabled': false, /* This Checkbox or Radio Button is disabled */
'CheckBox_onChange': function(el) {},
'RadioButton_onChange': function(el) {}
}
})(jQuery);
(function($) {
$.panel = function(options, callback) {
$('input[type=checkbox], input[type=radio]').meRadioCheck({
CheckBox_onChange: function(el) {
/* some stuff here... */
window.console.debug('panel: ' + typeof(el) + ', onChange: ', [el])
}
});
}
})(jQuery);
i only get this in the console: panel: undefined, onChange: [undefined]
but i want to get the CheckBox or RadioButton. I hope someone can help me...
Thanks... and have a nice Weekend.
How about:
(function($) {
$.fn.meRadioCheck.defaults = {
'debug': false, /* debug-mode for plugin */
'checked': false, /* this checkbox or radio button is checked */
'disabled': false, /* this checkbox or radio button is disabled */
'CheckBox_onChange': null,
'RadioButton_onChange': null
};
$.fn.meRadioCheck = function(overrideOptions, callback) {
var plugin = this,
options = $.extend($.fn.meRadioCheck.defaults, overrideOptions);
return this.each(function() {
var $this = $(this),
name = $this.attr('name');
if ( !$this.data().meRadioCheckInitDone ) {
$this.on("change", function() {
if (typeof options.CheckBox_onChange === "function") {
options.CheckBox_onChange.call(this);
}
}).data("meRadioCheckInitDone", true);
}
$this.prop('checked', !!options.checked);
$this.prop('disabled', !!options.disabled);
});
};
})(jQuery);
Changes
Gotten rid of .init(), it did not seem to serve any purpose.
Used a .data() variable instead of calling an internal API function to check if initialization is through. Seems cleaner.
Gotten rid of the custom xonChange event - listening to change directly will work just as well if all you do is call another function from there.
Added a typeof check for the event callback.
Setting $this.prop() unconditionally seems cleaner than the if-then-else you do.
On a more general note, you seem to implement some sort of data binding. Maybe looking into a data binding framework like knockout.js is worth it.
I have written a small JQuery plugin that creates a dropdown box based on bootstrap. I have written it to where a data attribute supplies a url that produces the list items. After the ajax call, Jquery loops through the list items and inserts them into the dropdown menu. Here is what I do not understand, the plugin takes a div with the class of .combobox and appends the required html to make the combobox. It uses two functions, _create() and _listItems(). _create() actually adds the html and calls on _listItems() to make the ajax call and it returns the list items to be appended. Looks like this:
;(function ( $, window, document, undefined ) {
var Combobox = function(element,options) {
this.$element = $(element);
this.$options = $.extend({}, $.fn.combobox.defaults, options);
this.$html = {
input: $('<input type="text" placeholder="[SELECT]" />').addClass('form-control'),
button: $('<div id="test"/>').addClass('input-group-btn')
.append($('<button />')
.addClass('btn btn-default input-sm')
.append('<span class="caret"></span>'))
}
this.$list_type = this.$element.attr('data-type');
this.$url = this.$element.attr('data-url');
this.$defaultValue = this.$element.attr('data-default');
this._create();
this.$input = this.$element.find('input');
this.$button = this.$element.find('button');
this.$list = this.$element.find('ul')
this.$button.on('click',$.proxy(this._toggleList,this));
this.$element.on('click','li',$.proxy(this._itemClicked,this));
this.$element.on('mouseleave',$.proxy(this._toggleList,this));
if(this.$defaultValue) {
this.selectByValue(this.$defaultValue);
}
}
Combobox.prototype = {
constructor: Combobox,
_create: function() {
this.$element.addClass('input-group input-group-sm')
.append(this.$html.input)
.append(this._listItems())
.append(this.$html.button);
},
_itemClicked: function(e){
this.$selectedItem = $(e.target).parent();
this.$input.val(this.$selectedItem.text());
console.log(this.$element.find('[data-value="W"]'))
this._toggleList(e);
e.preventDefault();
},
_listItems: function() {
var list = $('<ul />').addClass('dropdown-menu');
$.ajax({
url: this.$url,
type: 'POST',
data: {opt: this.$list_type},
success:function(data){
$.each(data,function(key,text){
list.append($('<li class="listObjItem" data-value="'+text.id+'">'+text.value+'</li>'));
})
}
})
return list
},
selectedItem: function() {
var item = this.$selectedItem;
var data = {};
if (item) {
var txt = this.$selectedItem.text();
data = $.extend({ text: txt }, this.$selectedItem.data());
}
else {
data = { text: this.$input.val()};
}
return data;
},
selectByValue: function(value) {
var selector = '[data-value="'+value+'"]';
this.selectBySelector(selector);
},
selectBySelector: function (selector) {
var $item = this.$element.find(selector);
if (typeof $item[0] !== 'undefined') {
this.$selectedItem = $item;
this.$input.val(this.$selectedItem.text());
}
else {
this.$selectedItem = null;
}
},
enable: function () {
this.$input.removeAttr('disabled');
this.$button.children().removeClass('disabled');
this.$button.on('click',$.proxy(this._toggleList,this));
},
disable: function () {
this.$input.attr('disabled', true);
this.$button.children().addClass('disabled');
this.$button.off('click',$.proxy(this._toggleList,this));
},
_toggleList: function(e) {
if(e.type == 'mouseleave') {
if(this.$list.is(':hidden')) {
return false;
} else {
this.$list.hide();
}
} else {
this.$list.toggle();
e.preventDefault();
}
}
}
$.fn.combobox = function (option) {
return this.each(function () {
if (!$.data(this, 'combobox')) {
$.data(this, 'combobox',
new Combobox( this, option ));
}
});
};
$.fn.combobox.defaults = {};
$.fn.combobox.Constructor = Combobox;
})( jQuery, window, document );
The problem is that after the items are appended to the DOM, everything is selectable accept the list items. I currently have an .on() statement that binds the click event with the list item. To test this out I have used console.log(this.$element.find('[data-value="W"]') and it does not return an element, however if I place that same console log in the click callback of the list item it will return the element and it is selectable. Am I doing something wrong?
EDIT
I have pasted the entire plugin to save on confusion.
I'm using backbone and lazy loading views in a single page application as I need them. However, it appears doing this seems to be confusing the way backbone knows what my 'el' is when setting up events. Using the view definition below, I'm trying to get the code that fires on the submit button click or the input fields changing but right now, neither appear to work.
$(document).ready(function () {
editaddressView = Backbone.View.extend({
elementReady: false,
initialize: function () {
this.model = window.AccountData;
this.listenTo(this.model, "change", this.render);
if ($('#section-editaddress').length == 0) {
// Load UI
$('#ajax-sections').prepend('<div class="section" id="section-editaddress" style="display: none;"></div>');
}
this.el = $('#section-editaddress');
},
events: {
"click #edit-address-submit": "beginSaving",
"change input": "updateModel",
"change select": "updateModel"
},
render: function () {
$(this.el).find("[name=address]").val(this.model.get('owner_address1'));
// ...
return this;
},
switchTo: function () {
// Set menu state
$('.js-NavItem').removeClass('active');
$('#sN-li-personal').addClass('active');
if (this.options.isPreLoaded)
this.elementReady = true;
if (this.elementReady) {
this.renderSwitch();
}
else {
var model = this;
$('#section-editaddress').load('/ajax/ui/editaddress', function (response, status, xhr) {
if (status == "error") {
$('#page-progress-container').fadeOut('fast', function () {
$('#page-load-error').fadeIn('fast');
});
} else {
$('#section-editaddress').find('.routedLink').click(function (e) {
window.Router.navigate($(this).attr('href'), true);
return false;
});
model.delegateEvents();
model.elementReady = true;
model.render(); // First render
model.renderSwitch();
}
});
}
},
renderSwitch: function () {
// Abort showing loading progress if possible
if (window.firstRunComplete) {
clearTimeout(window.pageHide);
// Change screen - Fade progress if needed
$('#page-progress-container').fadeOut('fast', function () {
$('#page-load-error').fadeOut('fast');
var sections = $(".section");
var numSections = sections.length;
var i = 0;
sections.hide('drop', { easing: 'easeInCubic', direction: 'left' }, 350, function () {
i++;
if (i == numSections) {
$('#section-editaddress').show('drop', { easing: 'easeInExpo', direction: 'right' }, 350).removeClass('hidden');
$.scrollTo($('#contentRegion'), 250, { margin: true });
}
});
});
}
// Switch complete
window.changingPage = false;
},
updateModel: function () {
var changedItems = {};
if (this.model.get('csrf') != $(this.el).find("[name=csrf]").val())
changedItems.csrf = $(this.el).find("[name=csrf]").val();
// ...
},
beginSaving: function () {
alert('test');
}
});
});
Can anyone see what I've missed?
Whenever you need to change or modify the DOM element of a BackboneJS view manually, you should use setElement rather than setting the property directly. It moves all of the event handlers to the newly attached DOM element and also sets the $el property. In addition, the function also detaches any existing event handlers.
So, in the code you pasted, you'd just change it to:
this.setElement($('#section-editaddress'));
i am working on a webpage that consists a JQuery item and a Knockout Item.
basically the view has a select field and a sevond view that is being updated by the select value change.
also i have a textbox search field with jquery autocomplete.
What i want to do is when i press enter after on the search box, the javascript will update the ko.observable value and trigger the ther updates yet its not working. i've managed to trigger keypress but i cant update and trigger the update..
Heres the code:
function Station(data){
var self = this;
this.userId = ko.observable(data.userid);
this.displayName = ko.observable(data.displayName);
this.email = ko.observable(data.email);
this.redirectURL = ko.computed(function(){
return "/someurl/somerequest?userId="+self.userId();
});
this.selectText = ko.computed(function(){
return self.displayName();
})
}
function currentStation(index)
{
return self.stations()[index];
}
function StationViewModel(){
var self = this;
self.stations = ko.observableArray([]);
$("#stationSelect").attr("disabled,true");
$.getJSON("#{someurl.getStationList()}",function(allData){
var mappedStations = $.map(allData,function(item)
{
return new Station(item);
});
self.stations(mappedStations);
$("#stationSelect").attr("disabled,false");
});
url = "/someurl/somerequest?userId=";
this.selectedStation = ko.observable();
this.redirectToStation = function(){
var linkToSend =
alert(self.selectedStation.redirectURL());
}
<!-- THIS IS THE CODE THAT HAS TO UPDATE THE FIELD BUT IT DOESN'T-->
this.getStation = function(event)
{
for(i = 0; i<this.stations().length;i++)
{
if(this.stations()[i].userId()==$("#search").val())
{
self.selectedStation = ko.observable(this.stations()[i]); //Am i doing it right?
}
}
};
}
<!-- This is the code That handles the click event inside the textbox. its working -->
ko.bindingHandlers.executeOnEnter = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var allBindings = allBindingsAccessor();
$(element).keypress(function (event) {
var keyCode = (event.which ? event.which : event.keyCode);
if (keyCode === 13) {
allBindings.executeOnEnter.call(viewModel);
return false;
}
return true;
});
}
};
ko.applyBindings(new StationViewModel());
</script>
Instead of
self.selectedStation = ko.observable(this.stations()[i]);
do
self.selectedStation(this.stations()[i]);
Hope this helps!
What I have done in the past to get the Enter key working is to wrap the <input> in a <form> tag
so from
<input type="text" data-bind="value:myValue"></input>
to
<form>
<input type="text" data-bind="value:myValue"></input>
</form>