Every time I press a key on my keyboard, it sets $(this).val(""); to null and the score variable will be -2.
initialize: function() {
var score = 0;
var wrapper = $('<div>')
.css({
position:'fixed',
top:'0',
left:'0',
width:'100%',
height:'100%'
});
this.wrapper = wrapper;
var self = this;
var text_input = $('<input>')
.addClass('form-control')
.css({
'border-radius':'4px',
position:'absolute',
bottom:'0',
'min-width':'80%',
width:'80%',
'margin-bottom':'10px',
'z-index':'1000'
}).keyup(function() {
var words = self.model.get('words');
for(var i = 0;i < words.length;i++) {
var word = words.at(i);
var typed_string = $(this).val();
var string = word.get('string');
if(string.toLowerCase().indexOf(typed_string.toLowerCase()) === 0) {
word.set({highlight:typed_string.length});
if(typed_string.length === string.length) {
$(this).val("");
score+=10;
$("#dialog").dialog('option', 'title', 'Score : '+score);
}
}
else {
word.set({highlight:0});
$(this).val(""); // problem
score-=2; // problem
$("#dialog").dialog('option', 'title', 'Score : '+score); // problem
}
}
});
$(this.el)
.append(wrapper
.append($('<form>')
.attr({
role:'form'
})
.submit(function() {
return false;
})
.append(text_input)));
text_input.css({left:((wrapper.width() - text_input.width()) / 2) + 'px'});
text_input.focus();
this.listenTo(this.model, 'change', this.render);
},
When I remove the code that causes the problem, it works perfectly every time. inputting the right word and giving the var score score of +10.
How the keyup event works?
The keyup event is triggered every time a key is released.
This means that if the target word is haromy, when typing the h, the event is triggered and the code in the callback is run.
It means that the following will always be false if typing the first letter wrong.
"haromy".toLowerCase().indexOf("f".toLowerCase()) === 0
So the user types a letter, it's not the same first letter, so the field is emptied immediatly by $(this).val("").
Maybe use another event?
If you want to check once the user unfocus the input, the blur event would work.
If you want to make the check when the user clicks a button, use a click event on a new button.
How to stylize a JavaScript application?
Do not set the initial CSS using jQuery's css function. Keep the styling in a CSS file linked in the HTML.
Using the css function only clutters your application logic, makes it difficult to maintain and delay the application of the style to after the JavaScript execution.
How to bind jQuery events with Backbone?
I removed the backbone.js tag from the question as it's irrelevant, but seeing that you're using it and that it could be improved a lot, I'll throw additional information here.
When using Backbone, don't bind events using jQuery directly. Use the Backbone view's events hash.
Your view could look like this:
var View = Backbone.View.extend({
template: '<div class="wrapper"><input class="form-control" /></div>'
events: {
"blur input": "onInputBlur"
},
onInputBlur: function() {
var words = this.model.get('words').each(function(word) {
var typed_string = this.$input.val(),
string = word.get('string');
// Check each word and score here
}, this);
},
initialize: function() {
this.score = 0;
this.listenTo(this.model, 'change', this.render);
},
render: function() {
this.$el.html(this.template);
this.$wrapper = this.$('.wrapper');
this.$input = this.$('input').focus();
return this;
},
});
With styles out, the CSS file would be:
.wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.input {
border-radius: 4px;
position: absolute;
bottom: 0;
min-width: 80%;
width: 80%;
margin-bottom: 10px;
z-index: 1000;
}
Related
The following code is not working. I would like to pass "this" (which would be .dot) from the click event to the function, findTargetSlideOfDot but I'm not sure how to go about doing that. thanks
$(document).on('click', '.dot', function() {
var targetSlide = findTargetSlideOfDot;
changeSlide(targetSlide);
})
function findTargetSlideOfDot() {
// find the slide number the user wants to see
var get = $.grep(this.className.split(" "), function(v, i){
return v.indexOf('slide_') === 0;
}).join();
var targetSlide = '.' + get;
return targetSlide;
}
function changeSlide(targetSlide) {
// hide current slide and dot
$('.slide.active, .dot.active').removeClass('active');
$(targetSlide).addClass('active');
}
Here:
$(document).on('click', '.dot', function() {
var targetSlide = $(this);
changeSlide(targetSlide);
})
function changeSlide(targetSlide) {
// hide current slide and dot
//$('.slide.active, .dot.active').removeClass('active');
$(targetSlide).toggleClass('active');
}
.dot{
width: 100px; border: 1px solid black;
padding: 10px; margin: 5px;
}
.dot.active{
background-color: gray;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="dot">test</div>
Give this a shot, modify your click function like this:
$(document).on('click', '.dot', function(e) {
// e.target should refer to the element with the dot class
})
Here's a functional code pen too: http://codepen.io/csavage1994/pen/Mppxaa
You may want to pass it as a parameter an actually call that function. And the improvement shown by Colton should be applied as well:
$(document).on('click', '.dot', function(e) {
var targetSlide = findTargetSlideOfDot( e.target );
changeSlide(targetSlide);
})
function findTargetSlideOfDot( element ) {
// find the slide number the user wants to see
var get = $.grep(element.className.split(" "), function(v, i){
return v.indexOf('slide_') === 0;
}).join();
var targetSlide = '.' + get;
return targetSlide;
}
function changeSlide(targetSlide) {
// hide current slide and dot
$('.slide.active, .dot.active').removeClass('active');
$(targetSlide).addClass('active');
}
You can choose between:
$(document).on('click', '.dot', function(e) {
findTargetSlideOfDot(e.target);
});
which gives you the element that triggered the event (was clicked)
and:
$(document).on('click', '.dot', function(e) {
findTargetSlideOfDot(e.currentTarget);
});
which gives you the element that had the event listener registered.
I am having difficulty properly clearing a timer function from within a hover event. I have two divs. They are siblings and the next sibling should display on hover of the other. I can't determine where I am messing up, but what I have figured out is that each time I hover over the main-item that it creates an entirely new timer with setTimeout. So the first iteration works fine, a second hover will trigger twice and so forth.
.main-item {
width: 300px;
height: 100px;
background: #000;
}
.sub-item {
display: none;
width:450px;
height: 75px;
background: red;
&.open {
display: block;
}
}
<div>
<div class="main-item">
</div>
<div class="sub-item"></div>
</div>
var timer;
$('.main-item').hover(function() {
var $this = $(this);
var $sub = $this.next();
$sub.addClass('open');
}, function() {
var $this = $(this);
var $sub = $this.next();
$sub.hover(function() {
var $this = $(this);
clearTimeout($this.data('timerId'));
timer = null;
console.log(timer);
}, function() {
var $this = $(this);
timer = setTimeout(function() {
$this.removeClass('open');
alert('this triggered');
}, 2000);
$this.data('timerId', timer);
});
});
The issue isn't the timer, but with binding event handlers.
Each time you mouse off of .main-item you bind a hover handler to .sub-item. You either need to remove the previous handler, or set a boolean to remember that you have binded the hover handler, or use the jquery function one with mouseenter and mouseoff events, there are many ways to solve this problem.
Using $sub.off() to remove previous handler.
https://jsfiddle.net/p2wtonac/2/
Using boolean to bind once.
https://jsfiddle.net/p2wtonac/3/
I want to apply animations when certain properties change with my knockout models (specifically a movement). I need these animations to be synchronous, if there is more than one going on things will get very confusing for the user.
I would like to use a knockout custom binding to do this, as it should make my code easier to understand, but if I do that I can't provide a callback function to the jquery animation. I know that I can't have true synchronous behavior due to javascript limitations, but I can't figure out how to fake it.
The behavior I want:
http://jsfiddle.net/3fLvpxLc/2/
$("#e1").animate({left: 50}, "slow", function() {
// more animations
}
The version with synchronization problems:
http://jsfiddle.net/hrwsd1z3/1/
ko.bindingHandlers.position = {
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
$(element).animate({left: valueUnwrapped}, "slow");
}
}
jQuery queues are your friend. With them you can serialize asynchronous animations.
Usually they are used implicitly for all animation effects you do on one element, i.e. tied to the animated element itself:
$("element").show("slow").animate({left: 25});
But you can use them explicitly, too. queue adds an animation to the queue, the next callback dequeues the next animation (you can conveniently pass it as the complete handler). That way you can tie the animations to a different element than the animated one:
$("#container").queue(function (next) {
$("element").show("slow", next);
}
$("#container").queue(function (next) {
$("element").animate({left: 25}, next);
}
With that knowledge the task becomes simple:
ko.bindingHandlers.syncPosition = {
init: function(element, valueAccessor) {
var newPosition = ko.toJS(valueAccessor());
// set element to its initial position
$(element).css(newPosition);
},
update: function(element, valueAccessor) {
var newPosition = ko.toJS(valueAccessor());
// queue position update as animation to a common element, e.g. the body
$(document.body).queue(function( next ) {
$(element).animate(newPosition, "slow", next);
});
}
};
function Item(id, top, left) {
this.id = ko.observable(id);
this.position = {
top: ko.observable(top),
left: ko.observable(left)
};
}
function VM(params) {
var self = this;
self.elements = ko.observableArray([
new Item("e1"),
new Item("e2"),
new Item("e3")
]);
}
var vm = new VM();
ko.applyBindings(vm);
vm.elements()[0].position.left(50);
vm.elements()[1].position.left(75);
vm.elements()[2].position.left(25);
vm.elements()[1].position.left(125);
vm.elements()[2].position.top(10);
vm.elements()[1].position.top(20);
vm.elements()[0].position.top(30);
vm.elements()[0].position.left(0);
vm.elements()[1].position.left(0);
vm.elements()[2].position.left(0);
div.container {
position: relative;
}
div.container > div {
width: 20px;
height: 20px;
position: absolute;
}
#e1 {
background-color: blue;
}
#e2 {
background-color: red;
}
#e3 {
background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="container" data-bind="foreach: elements">
<div data-bind="syncPosition: position, attr: {id: id}"></div>
</div>
I am trying to animate the width of something when the .change() function is called, but it doesn't seem to be working.
Any idea why?
Here is my code:
$(document).ready(function(){
$('#code').change(function(){
//on change animate a width of +16px increase.
$(this).animate({width: '+=16'});
});
});
Here is a js fiddle with the issue recreated: http://jsfiddle.net/BUSSX/
If you really want a change event for input controls, then here's a jQuery plug-in method I wrote a little while ago that does this and works for nearly all ways that the content of the input control can be changed including drag/drop, copy/paste, typing, etc... It takes advantage of newer events that help with this if they exist, otherwise it falls back to listening for lots of other events and looking to see if the data has changed.
(function($) {
var isIE = false;
// conditional compilation which tells us if this is IE
/*#cc_on
isIE = true;
#*/
// Events to monitor if 'input' event is not supported
// The boolean value is whether we have to
// re-check after the event with a setTimeout()
var events = [
"keyup", false,
"blur", false,
"focus", false,
"drop", true,
"change", false,
"input", false,
"textInput", false,
"paste", true,
"cut", true,
"copy", true,
"contextmenu", true
];
// Test if the input event is supported
// It's too buggy in IE so we never rely on it in IE
if (!isIE) {
var el = document.createElement("input");
var gotInput = ("oninput" in el);
if (!gotInput) {
el.setAttribute("oninput", 'return;');
gotInput = typeof el["oninput"] == 'function';
}
el = null;
// if 'input' event is supported, then use a smaller
// set of events
if (gotInput) {
events = [
"input", false,
"textInput", false
];
}
}
$.fn.userChange = function(fn, data) {
function checkNotify(e, delay) {
var self = this;
var this$ = $(this);
if (this.value !== this$.data("priorValue")) {
this$.data("priorValue", this.value);
fn.call(this, e, data);
} else if (delay) {
// The actual data change happens aftersome events
// so we queue a check for after
// We need a copy of e for setTimeout() because the real e
// may be overwritten before the setTimeout() fires
var eCopy = $.extend({}, e);
setTimeout(function() {checkNotify.call(self, eCopy, false)}, 1);
}
}
// hook up event handlers for each item in this jQuery object
// and remember initial value
this.each(function() {
var this$ = $(this).data("priorValue", this.value);
for (var i = 0; i < events.length; i+=2) {
(function(i) {
this$.on(events[i], function(e) {
checkNotify.call(this, e, events[i+1]);
});
})(i);
}
});
}
})(jQuery);
Then, your code would look like this:
$(document).ready(function(){
$('#code').userChange(function(){
//on change animate a width of +16px increase.
$(this).animate({width: '+=16'});
});
});
In looking at your code, you are increasing the width of the input control by 16px on every change. You probably should be looking at the number of characters in the control and assessing what to do about the width based on that because this will make things wider event if the user hits the backspace key. I'd probably do something like this that grows the item as content is added, but doesn't shrink it:
$(document).ready(function(){
$('#code').userChange(function(){
// on change animate width as chars are added
// only grow it when the width needs to be larger than it is currently
var item = $(this);
var origWidth = item.data("initialWidth");
var curWidth = item.width();
if (!origWidth) {
origWidth = curWidth;
item.data("initialWidth", origWidth);
}
var newWidth = origWidth + (8 * item.val().length);
if (newWidth > curWidth) {
item.stop(true, true).animate({width: newWidth}, 500);
}
});
});
Working code example: http://jsfiddle.net/jfriend00/BEDcR/
If you want the userChange method to execute when you programmatically set the value with .val(), then you can make your own method for that:
$(document).ready(function(){
function updateWidth() {
// on change animate width as chars are added
// only grow it when the width needs to be larger than it is currently
var item = $(this);
var origWidth = item.data("initialWidth");
var curWidth = item.width();
if (!origWidth) {
origWidth = curWidth;
item.data("initialWidth", origWidth);
}
var newWidth = origWidth + (8 * item.val().length);
if (newWidth > curWidth) {
item.stop(true, true).animate({width: newWidth}, 500);
}
}
$('#code').userChange(updateWidth);
$.fn.valNotify = function(value) {
this.val(value);
this.each(function() {
updateWidth.call(this);
});
return this;
}
});
Then, you can change your values with this and it will automatically resize too:
$("#code").valNotify("foo");
If based on your previous question HTML markup :
<button class="I button">I</button>
<button class="O button">O</button>
<input id="code" type="text" disabled />
So if you want to animate the width of the textbox, you need to animate it when click the button:
$('.button').click(function(event) {
var text = $(this).text();
$('input:text').val(function(index, val) {
return val + text;
});
$('#code').animate({width: '+=16'});
});
Working Demo
If based on your above question HTML markup, you need to use keyup instead of change as well as include the jQuery library in the jsFiddle:
$(document).ready(function(){
$('#code').keyup(function(){
//on change animate a width of +16px increase.
$(this).animate({width: '+=16'});
});
});
Updated Demo
You just need to check which key was pressed:
$(document).ready(function(){
$('#code').keyup(function(e){
//on change animate a width of +16px increase.
if(e.keyCode == 8) { // Backspace pressed
$(this).animate({width: '-=16'});
} else {
$(this).animate({width: '+=16'});
}
});
});
Updated Demo
You forgot to load jQuery, working fine here http://jsfiddle.net/BUSSX/13/ - also you need the click event. Or even better use the keyup event so that as soon as something is typed, the textbox increases in width - http://jsfiddle.net/BUSSX/15/
you have a bad implementation check http://jsfiddle.net/3dSZx/ and you need add jquery to fiddle
JS
$(document).ready(function(){
$('#push').click(function(){
//on change animate a width of +16px increase.
$('#code').animate({ width: '+=16'});
});
});
IrfanM, instead of incrementing by a fixed amount, you might consider incrementing by just the right amount to accommodate each character as it is typed.
Unless I've overcomplicated things (not completely unknown), this is moderately tricky.
In the following jQuery plugin :
text input fields are each given a hidden <span> with the same font-family and font-size as its respective input element.
the <span> elements act as "measuring sticks" by accepting a copy of their input field's entire text every time a character is typed.
the width of the <span> plus one generous character width is then used to determine the width of the input field.
Here's the code :
(function ($) {
var pluginName = 'expandable';
$.fn[pluginName] = function () {
return this.each(function (i, input) {
var $input = $(input);
if (!$input.filter("input[type='text']").length) return true;
// Common css directives affecting text width
// May not be 100% comprehensive
var css = {
fontFamily: $input.css('fontFamily'),
fontSize: $input.css('fontSize'),
fontStyle: $input.css('fontStyle'),
fontVariant: $input.css('fontVariant'),
fontWeight: $input.css('fontWeight'),
fontSizeAdjust: $input.css('fontSizeAdjust'),
fontStretch: $input.css('fontStretch'),
letterSpacing: $input.css('letterSpacing'),
textTransform: $input.css('textTransform'),
textWrap: 'none'
};
var $m = $("<span/>").text('M').insertAfter($input).css(css).text('M').hide();
var data = {
'm': $m,
'w': $m.width()
};
$input.data(pluginName, data).keyup(function (e) {
$this = $(this);
var data = $this.data(pluginName);
var w = data.m.html($this.val().replace(/\s/g, " ")).width();
$this.css({
'width': w + data.w
});
}).trigger('keyup');
});
};
})(jQuery);
$(document).ready(function () {
$('input').expandable();
});
DEMO
This works because a <span> element automatically expands to accommodate its text, whereas an <input type="text"> element does not. A great feature of this approach is that the keystrokes don't need to be tested - the plugin automatically responds to character deletions in the same way it responds to character strokes.
It works with proportional and monospaced fonts and even responds appropriately to cut and paste.
The only precaution necessary is to convert spaces to non-breaking spaces, $nbsp;, otherwise HTML renders multiple spaces as a single space in the <span> element.
Of course, it you really want exactly 16px growth for every keystroke, then stick with what you already have.
I'm experiencing weird behavior with jquery ui autocomplete when using it to create a combobox. Whenever I click on the scrollbar to scroll through the list of results AND then click on my combobox button to close the results the results list closes and then opens again. I expect it to close the menu.
Steps to Repro
open jsfiddle demo
Type 'i' in the autocomplete OR hit the dropdown button.
Click on the vertical scroll to scroll the results
Click on the dropdown button
Script to Create Button
this.button = $("<button type='button'> </button>")
.attr({ "tabIndex": -1, "title": "Show all items" })
.insertAfter(input)
.button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
})
.removeClass("ui-corner-all")
.addClass("ui-corner-right ui-button-icon")
.click(function () {
// when i put breakpoint here, and my focus is not on input,
// then this if steatment is false????
if (input.autocomplete("widget").is(":visible")) {
input.autocomplete("close");
return;
}
// work around a bug (likely same cause as #5265)
$(this).blur();
// pass empty string as value to search for, displaying all results
input.autocomplete("search", "");
input.focus();
});
CSS (force long results menu to scroll)
.ui-autocomplete {
max-height: 100px;
overflow-y: auto;
/* prevent horizontal scrollbar */
overflow-x: hidden;
/* add padding to account for vertical scrollbar */
padding-right: 20px;
}
/* IE 6 doesn't support max-height
* we use height instead, but this forces the menu to always be this tall
*/
* html .ui-autocomplete {
height: 100px;
}
My solution could be closing the widget even if focus is transferred to widget itself and not the input element?
Any ideas how to modify this code so it behaves this way?
Based on issues with the various click and mouse events for the automplete widget, I came up with this: jsFiddle example.
jQuery:
var input = $('#txtComplete');
var data = [];
var isOpen = false;
function _init() {
for (var idx = 0; idx <= 100; idx++) {
data.push('item: ' + idx);
};
input.autocomplete({
source: data,
minLength: 0,
open: function(event, ui) {
isOpen = true;
},
select: function(event, ui) {
isOpen = false;
}
});
}
function afterInit() {
var button = $("<button type='button'> </button>").attr("tabIndex", -1).attr("title", "Show all items").insertAfter(input).button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
}).removeClass("ui-corner-all").addClass("ui-corner-right ui-button-icon").click(function(event) {
input.focus();
if (isOpen) {
input.autocomplete("close");
isOpen = false;
} else {
input.autocomplete("search", "");
event.stopImmediatePropagation();
}
});
}
$(window).click(function() {
input.autocomplete("close");
isOpen = false;
});
$(function() {
_init();
afterInit();
});
The problem is because of a work around in jquery ui autocomplete. There is a mousedown event setup to close the menu under certain conditions. In one of the conditions it checks to see if the item that raised the mousedown is part of the autocomplete widget. If not, it closes the menu. Since you are tacking on combobox behaviour and your button is not part of the autocomplete widget, a click on the button is closing the menu because of this event.
You can see the offending condition with the reason why it is there starting at line 205 in the autocomplete source on github. It is probably worth raising the issue on the jquery ui forums since their combobox demo has this bug too.
UPDATE
This replacement event is based off of jquery-ui 1.8.18. This event has changed and will very likely change again. You might need to update this code manually with each release if you go this route.
You can patch the mousedown event to not close the menu if it was your combo button that was clicked by running the following after you create your autocomplete (jsfiddle demo).
var input = $('#combotextbox').autocomplete(/*options*/);
input.data('autocomplete').menu.element.unbind('mousedown').mousedown(function(event) {
var self = input.data('autocomplete');
event.preventDefault();
// clicking on the scrollbar causes focus to shift to the body
// but we can't detect a mouseup or a click immediately afterward
// so we have to track the next mousedown and close the menu if
// the user clicks somewhere outside of the autocomplete
var menuElement = self.menu.element[0];
if (!$(event.target).closest(".ui-menu-item").length) {
setTimeout(function() {
$(document).one('mousedown', function(event) {
var t = $(event.target);
if (event.target !== self.element[0] && event.target !== menuElement && !$.ui.contains(menuElement, event.target) && !t.hasClass('ui-combo-trigger') && !t.parent().hasClass('ui-combo-trigger')) {
self.close();
}
});
}, 1);
}
// use another timeout to make sure the blur-event-handler on the input was already triggered
setTimeout(function() {
clearTimeout(self.closing);
}, 13);
});
This removes the current mousedown event and then adds it back in with an added check to see if the element that triggered the event or its parent (button clicked or ui-icon inside the button is clicked) has a class ui-combo-trigger.
The code to create your button is relatively unchanged. We just need to add the new class ui-combo-trigger.
var button = $("<button type='button'> </button>").attr("tabIndex", -1).attr("title", "Show all items").insertAfter(input).button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
}).removeClass("ui-corner-all").addClass("ui-corner-right ui-button-icon ui-combo-trigger").click(function(event) {
// when i put breakpoint here, and my focus is not on input,
// then this if steatment is false????
if (input.autocomplete("widget").is(":visible")) {
input.autocomplete("close");
return;
}
// work around a bug (likely same cause as #5265)
$(this).blur();
// pass empty string as value to search for, displaying all results
input.autocomplete("search", "");
input.focus();
event.stopImmediatePropagation();
});
Try this jsfiddle. I think it ll help you.
var input = $('#txtComplete');
var data = [];
var openCheck = false;
function _init() {
for (var idx = 0; idx <= 100; idx++) {
data.push('item: ' + idx);
};
input.autocomplete({
source: data,
minLength: 0,
open: function(event, ui) {
openCheck = true;
},
select: function(event, ui) {
openCheck = false;
}
});
}
function afterInit() {
var button = $("<button type='button'> </button>").attr("tabIndex", -1).attr("title", "Show all items").insertAfter(input).button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
}).removeClass("ui-corner-all").addClass("ui-corner-right ui-button-icon").click(function(event) {
if (openCheck) {
input.autocomplete("close");
openCheck = false;
} else {
input.autocomplete("search", "");
}
});
}
$(function() {
_init();
afterInit();
});
Brian explained the problem very good. With jquery ui 11 you can do something like:
wasOpen = false;
$button
.mousedown(function() {
wasOpen = input.autocomplete( "widget" ).is( ":visible" );
})
.click(function() {
input.focus();
// Close if already visible
if ( wasOpen ) {
return;
}
see example at http://jqueryui.com/autocomplete/#combobox