I'm new to JS and I have recently ran into a problem with the setTimeout function while using the Jquery-UI Datepicker.
I would like to get a value from within a function referenced in a setTimeout function and use that value on other areas of my code. However, because setTimeout delays the code execution, I can't seem to assign and use the said value.
Here's part of the code
$calendar.datepicker({
inline: true,
onSelect: function (dateText,inst) {
var startDate;
window.setTimeout(function(){getStartDate();}, 1);
// ... Do something with startDate. No matter how I try, startDate is always undefined.
function getStartDate () {
var r = $calendar
.find('.ui-datepicker-current-day')
.parent()
.find('.selectable')
.first()
.children()
.text();
startDate = new Date(date.setDate(r));
return startDate;
}
},
});
I need to use the setTimeout function otherwise the value returned from Jquery UI datepicker is wrong.
Ideally, I'm looking for startDate to be the value returned from the function getStartDate() that is set within the timeout function.
What I'm writing is obviously wrong and I have no idea how to return the startDate value from within the timeout function and use it elsewhere
Help is very much appreciated. Thanks!
Related
I totally get why Turbolinks 5 is awesome and if you're reading it, you probably do as well, but I am very frustrated with how badly it plays with the other scripts on the block.
To date, there is no simple explanation (human readable) that shows how to wrap existing jQuery scripts in a way that would allow them to function.
Take for example this one: https://github.com/Bttstrp/bootstrap-switch. It's well written, simple to understand. You load the js and css to your assets pipeline and instantiate it on some page.
# view.html.erb
<input type="checkbox" class="switch"> switch button
<script type="text/javascript">
$(".switch").bootstrapSwitch();
</script>
you go to view.html, click another page, click back and you see two buttons.
Next, you spend 5 hours looking for a way to have Turbolinks load the instance of bootstrapSwitch only once if not loaded before. Well, even if you do, the functionality will be gone. Clicking it will not work.
$(document).on("turbolinks:load", function()... will load it on every Turbolink visit, and for now, the only way I could make it work and not create duplicates was to disable cache on view.html with
<%= content_for :head do %>
<meta name="turbolinks-cache-control" content="no-cache">
<% end %>
Which feels kinda stupid.
I think it all has something to do with using idempotent - https://github.com/turbolinks/turbolinks#making-transformations-idempotent but how do you practically do this?
Could someone please take this simple plugin as an example and share a simple, elegant solution for making it work which we can then reproduce with other scripts?
Developing apps with Turbolinks does require a particular approach in order to get things running smoothly. Due to differences the way pages are loaded and cached, some patterns of running scripts won't behave in the same way with Turbolinks vs. without. This may seem unfriendly at first, and the "gotchas" can be frustrating, but I've found that with a little understanding, it encourages more organised, robust code :)
As you have figured out, the problem with duplicate switches is that the plugin is being called more than once on the same element. This is because Turbolinks caches a page just before navigating away from it, and so the cached version includes any dynamically added HTML[1] e.g. stuff added via plugins. When navigating back/forward, the cached version is restored, and the behaviour is duplicated :/
So how to fix this? When working with code which adds HTML or event listeners, it is generally a good idea to teardown behaviours before the page is cached. The Turbolinks event for that is turbolinks:before-cache. So your setup/teardown might be:
// app/assets/javascripts/switches.js
$(document)
.on('turbolinks:load', function () {
$('.switch').bootstrapSwitch()
})
.on('turbolinks:before-cache', function () {
$('.switch').bootstrapSwitch('destroy')
})
This is a bit difficult to test since all the setup and teardown is done in event handlers. What's more, there maybe many more cases like this, so to prevent repitition, you may want to introduce your own "mini-framework" for setting up and tearing down functionality. The following walks through creating a basic framework.
Here's is what we'll aim for: calling window.App.addFunction with a name and a function registers a function to call. That function gets the elements and calls the plugin. It returns an object with a destroy function for teardown:
// app/assets/javascripts/switches.js
window.App.addFunction('switches', function () {
var $switches = $('.switch').bootstrapSwitch()
return {
destroy: function () {
$switches.bootstrapSwitch('destroy')
}
}
})
The following implements addFunction, storing added functions in the functions property:
// app/assets/javascripts/application.js
// …
window.App = {
functions: {},
addFunction: function (name, fn) {
this.functions[name] = fn
}
}
We'll call each function when the app initializes, and store the result of each function call in the results array if it exists:
// app/assets/javascripts/application.js
// …
var results = []
window.App = {
// …
init: function () {
for (var name in this.functions) {
var result = this.functions[name]()
if (result) results.push(result)
}
}
}
Tearing down the app involves destroying calling destroy (if it exists) on any results:
// app/assets/javascripts/application.js
// …
window.App = {
// …
destroy: function () {
for (var i = 0; i < results.length; i++) {
var result = results[i]
if (typeof result.destroy === 'function') result.destroy()
}
results = []
}
}
Finally we initialise and teardown the app:
$(document)
.on('turbolinks:load', function () {
window.App.init.call(window.App)
})
.on('turbolinks:before-cache', window.App.destroy)
So to put this all together:
;(function () {
var results = []
window.App = {
functions: {},
addFunction: function (name, fn) {
this.functions[name] = fn
},
init: function () {
for (var name in this.functions) {
var result = this.functions[name]()
if (result) results.push(result)
}
},
destroy: function () {
for (var i = 0; i < results.length; i++) {
var result = results[i]
if (typeof result.destroy === 'function') result.destroy()
}
results = []
}
}
$(document)
.on('turbolinks:load', function () {
window.App.init.call(window.App)
})
.on('turbolinks:before-cache', window.App.destroy)
})()
Functions are now independent of the event handler that calls them. This decoupling has a couple of benefits. First it's more testable: functions are available in window.App.functions. You can also choose when to call your functions. For example, say you decide not to use Turbolinks, the only part you'd need to change would be when window.App.init is called.
[1] I think this is better than the default browser behaviour (where pressing "Back" returns the user back to the page as it was when it was first loaded). A Turbolinks "Back" returns the user back to the page as they left it, which is probably what a user expects.
I would like to ask you for advice.
On one page I have many jQuery UI datepicker`s. They are visible in Bootstrap Modal when user click on button.
Unfortunately this page is loading very slow(Especially in IE8). I know that datepicker in IE8 is causing slowness but sometime I receive that script stop working.
This is my datetimepicker in Knockout like a custom bindingHandlers
ko.bindingHandlers.datetimepicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datetimepickerOptions || {};
$(element).datetimepicker(options);//Line 5
//handle the field changing
ko.utils.registerEventHandler(element, "change", function () {
var observable = valueAccessor();
observable($(element).datetimepicker("getDate"));
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).datetimepicker("destroy");
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
current = $(element).datetimepicker("getDate");
if (value - current !== 0) {
// LINE 23 $(element).datetimepicker("setDate", value);
}
}
};
I comment Line 23 because before I had the same issue with performance. But then I had the picker like that
<input data-bind="datetimepicker: date,datepickerOptions: { minDate: new Date() }"/>
When I comment it everything works great.
Now I change the do (because I want to have visible datepicker when the modal is shown) and I have the same issue.
When I comment LINE 5 there is no slowness but also my datepicker is not vissible.
Can I call the binding of the datepicker's when user click on button to see the Modal? In this way the binding for datepicker's woudn't be initialized during the page loading?
If I change the jQuery UI datepicker to bootstrap datepicker, there will be some changes in performance?
You could potentially "lazily" initialize your datepicker's. For example, to go along with your existing datepicker binding, you could create a lazyDatepicker binding.
Something like:
ko.bindingHandlers.lazyDatepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//keep the field's value in sync until the datepicker binding has been initialized
var updater = ko.computed(function() {
var options = ko.unwrap(allBindingsAccessor()),
value = $.datepicker.formatDate(options.dateFormat || "mm/dd/yy", ko.unwrap(valueAccessor()));
$(element).val(value);
});
//use "one" so this only fires a single time on initial focus
$(element).one("focus", function() {
//binding will now keep value in sync
updater.dispose();
//apply the actual datepicker
ko.applyBindingAccessorsToNode(element, {
datepicker: valueAccessor,
datepickerOptions: function() { return allBindingsAccessor.get("datepickerOptions"); }
})
});
}
};
Really this just applies the datepicker binding when the field is focused. I also put some logic in there to keep the field up-to-date with the model until the datepicker is actually initialized (the updater computed).
Here is a fiddle: http://jsfiddle.net/rniemeyer/GvLfV/
First up: check this fiddle.
I have sortable array of elements created with the Knockout sortable library. When I initially apply the binding the cleditor initializes fine.
However, when sortable elements are sorted, the cleditor fails to re-initialize (I'm not sure what happens but cleditor fails). The cleditor just displays "true" instead of actual value in Firefox, and nothing in all other browsers.
I'm trying to figure out where the problem is, whether it is on the custom binding, or jQuery-UI, or the Knockout sortable library?
I am not recieving any errors in my console.
ko.bindingHandlers.cleditor = {
init: function(element, valueAccessor, allBindingsAccessor) {
var modelValue = valueAccessor(),
allBindings = allBindingsAccessor();
var $editor = jQuery(element).cleditor({
height: 50,
controls: "bold italic underline | bullets numbering | undo redo"
});
$editor[0].change(function() {
var elementValue = $editor[0].doc.body.innerHTML;
if (ko.isWriteableObservable(modelValue)) {
modelValue(elementValue);
} else {
if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers'].cleditor) {
allBindings['_ko_property_writers'].cleditor(elementValue);
}
}
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()) || '',
$editor = jQuery(element).cleditor();
if ($editor[0].doc.body.innerHTML !== value) {
//$editor[0].doc.body.innerHTML = value;
$editor[0].doc.body.innerHTML = value;
$editor[0].focus();
}
}
};
How can I make the cleditor to work, even after the elements are sorted?
I found this resource, but I couldn't find anything wrong in code as said in that topic.
The link you provided was helpful. The CLEditor refresh method is the right way to update it after it's dragged. It just needs to be done at the correct time, using the sortable stop event.
stop: function(event, ui) {
$(ui.item).find("textarea").cleditor()[0].refresh();
}
http://jsfiddle.net/mbest/rh8c2/1/
I also worked to integrate this into your cleditor binding. In the init function:
jQuery(document).on('sortstop', function(event, ui) {
if (jQuery.contains(ui.item[0], element)) {
jQuery(element).cleditor()[0].refresh();
}
});
I also made a change in the update function to keep the <textarea> value in sync, because refresh updates the editor's value from the <textarea>:
$editor[0].updateTextArea();
http://jsfiddle.net/mbest/jw7Je/7/
Non-working code:
$('#datepicker').datepicker(
{ onSelect: function(dateText, dpo){
var selectedDate = dpo.getDate();
});
From the API, I see that I can use var currentDate = $( ".selector" ).datepicker( "getDate" ); in lieu of the non-working code I posted. The onSelect documentation states "The function receives the selected date as text and the datepickerinstance as parameters". If the date picker is one of the parameters, why is the above code incorrect?
The inst parameter of the onSelect is an internal object that represent the current state of the datepicker. Normally you don't need to use it, you can use this, it refers to the original input field.
Called when the datepicker is selected. The function receives the
selected date as text and the datepicker instance as parameters. this
refers to the associated input field.
Code:
$('#datepicker').datepicker({
onSelect: function (dateText, dpo) {
var selectedDate = $(this).datepicker( "getDate" );
console.log(selectedDate)
}
});
Demo: http://jsfiddle.net/IrvinDominin/Y3hR6/
I am trying to set a variable to the selected value of a select menu inside the select menus handler. The problem is that my variable isn't recognized within the context of the select handler and if I pass the select handler the variables context I can no longer access the value of the selected variable. Does any one know a work around?
function object(){
this.testVar;
this.handler;
}
function makeObject(){
var result = new object();
result.testVar;
result.handler = handler;
return result;
}
function handler(){
alert($(this).val());
alert(this.testVar);
//ultimately this is what I want to do
this.testVar = $(this).val();
}
//If I do it this way $(this).val() is defined but not testVar
$("#test-select").on("change", {
testdata: this.testVar
}, this.handler);
//If I do it this way testVar is defined but not $(this).val()
$("#test-select").on("change", {
testdata: this.testVar
}, $.proxy(this.handler,this));
You need to decide which context you want the handler to execute in, and pass the other context as a parameter to your handler function.
When the handler is called, 'this' will initially refer to the changed HTML element. When you are setting up the listener, 'this' will refer to the object setting it up.
One example would be:
function handler(obj) {
alert($(this).val()); // this is the test-select object.
alert(obj.testVar);
}
var self = this;
$("#test-select").on("change", {
testdata: this.testVar
}, function() { self.handler(self); });