Ember JS and jquery-ui scope of callbacks - jquery-ui

I'm using latest ember js and have the following view:
App.PeriodSelectorView = Em.View.extend
classNames: "period-selector"
templateName: "period_selector"
didInsertElement: ->
#$().draggable()
#setupTimeSlider()
setupTimeSlider: ->
#$(".time-slider").slider
value: 20
min: 20 # 0500 in the morning
max: 83 # 2045 in the evening
step: 1
slide: #updateTimeValue
updateTimeValue: (event, ui) ->
#set("time", ui.value)
Now when the slider is changed the callback fires, but this is set to window instead of the view instance. So calling this.set fails.
I already tried binding the method updateTimeValue to the instance using coffeescript's fat arrow (=>) operator, but it doesn't work.
How to get this point to the view instance instead of window?

Problem
I am not a coffeescript expert, but in my tests, it seems that using the => operator. Generate the _this = this, in the parent function scope. For example:
Using:
App = Ember.Application.create()
App.PeriodSelectorView = Em.View.extend
...
updateTimeValue: (event, ui) =>
#set("time", ui.value)
Generates the following:
var App,
_this = this;
App = Ember.Application.create();
App.PeriodSelectorView = Em.View.extend({
...
updateTimeValue: function(event, ui) {
// _this here is window object, this isn't what you want
return _this.set("time", ui.value);
}
});
Solution 1
Following this logic using an anonymous function you will get the expected result:
Updating to the following:
App = Ember.Application.create()
App.PeriodSelectorView = Em.View.extend
...
setupTimeSlider: ->
#$(".time-slider").slider
value: 20
min: 20 # 0500 in the morning
max: 83 # 2045 in the evening
step: 1
slide: (event, ui) => #slide now is an anonymous function
#set("time", ui.value)
Generates:
var App;
App = Ember.Application.create();
App.PeriodSelectorView = Em.View.extend({
...
setupTimeSlider: function() {
// now _this is the view context
var _this = this;
return this.$(".time-slider").slider({
value: 20,
min: 20,
max: 83,
step: 1,
slide: function(event, ui) {
// this will work
return _this.set("time", ui.value);
}
});
}
});
Solution 2
If you want to keep the name of the function, and the function declarion in the same place. You can use the jQuery.proxy:
App = Ember.Application.create
App.PeriodSelectorView = Em.View.extend
...
setupTimeSlider: ->
#$(".time-slider").slider
value: 20
min: 20 # 0500 in the morning
max: 83 # 2045 in the evening
step: 1
slide: Ember.$.proxy #updateTimeValue, #
updateTimeValue: (event, ui) ->
#set("time", ui.value)
I hope it helps

Related

select2 (remote data) throws exception because of typing to fast

I'm using select2 for loading remote data. I declared the minimumInputLength to 3 letters, so after that it will start searching.
Whenever I hit the fourth letter while typing fast I get an Javascript exception saying :
Sorry. An error occured while communicating with the server. Please try again later.
How can I avoid this? I already changed the quietMillis (waitTimesMs) to lower or higher (does this even have something to do with it?).
Every help is appreciate.
My code is like:
$(function () {
$("#Search").select2({
minimumInputLength: 3,
ajax: {
url: site,
dataType: "json",
quietMillis: waitTimeMs,
data: function (params) {
var page = (params.page || 1) - 1;
return {
searchText: params.term,
pageCount: 10,
page: page
};
},
processResults: function (data) {
var select2Data = $.map(data.Items, function (obj) {
obj.id = obj.ID;
obj.text = obj.Name;
return obj;
});
return {
results: select2Data,
pagination: { more: (data.PageNo * 10) < data.TotalCount }
};
}
Finally it works!
select2 changed "quietMillis" to "delay" so I could change quietMillis as big as I wanted and nothing changed at all...

How to update a collection on jQuery (drag and) drop

I'm building a Meteor app that let's the user organize lists of items in tags.
I use jQuery draggable and droppable to update a collection when a user drags an item from one tag to another.
I find it hard to understand how/where/when I should call the function. I've tried a few different options (including this "hacky way of doing it". The Blaze documentation mentions that functions can be called on DOM events, but lacks the drag and drop events that I'm looking for. I've currently settled on calling the function under Template.rendered, but that means the item can only be dropped once per render. I've tried to counter this with Tracker.autorun, but I don't think I understand how it works and the item can still only be dropped once per render.
How can I make the .item draggable several times per render?
Template.tag.rendered = function () {
//wait on subscriptions to load
if (Session.get('DATA_LOADED')) {
Tracker.autorun(function () {
$(".item").draggable({
revert: true,
start: function (event, ui) {
var movingItem = Blaze.getData(this)._id;
Session.set('movingItem', movingItem);
},
});
$(".tag").droppable({
hoverClass: 'droppable',
drop: function () {
var movingItem = Session.get('movingItem');
var acceptingTag = Blaze.getData(this)._id;
Items.update(movingItem, {
$set: {"parents": acceptingTag}
});
}
});
});
}
};
I found the solution.
By separating the .draggable and .droppable into two different Template.rendered the function is now correctly called each time an item is moved.
No need for the Tracker.autorun
Template.item.rendered = function () {
if (Session.get('DATA_LOADED')) {
$(".item").draggable({
revert: true,
start: function (event, ui) {
var movingItem = Blaze.getData(this)._id;
Session.set('movingItem', movingItem);
console.log('moving ' + movingItem);
},
});
}
};
Template.tag.rendered = function () {
if (Session.get('DATA_LOADED')) {
$(".tag").droppable({
hoverClass: 'droppable',
drop: function () {
var movingItem = Session.get('movingItem');
var acceptingTag = Blaze.getData(this)._id;
Items.update(movingItem, {
$set: {"parents": acceptingTag}
});
console.log('Dropped on ' + acceptingTag);
}
});
}
};

Duplicate Series in the legend when exporting

Sorry if this question has been asked before but I could not find a similar question that is related to my problem.
The issue I am experiencing is that when exporting to PNG, JPG etc. the series would double up. So if my on screen chart has four series plotted, when it comes to exporting it will have eight series in the legend.
I think the problem is related to 'load' event of the chart is being executed subsequent times when exporting.
$(function () {
Highcharts.setOptions({
global: {
useUTC: false
}
});
function fnFetchData(chart) {
// Just imagine an AJAX request has just been done to get a JSON response
// JSONData = $.getJSON('/FetchSales');
var JSONData
JSONData = {
seriesName: 'Sales 2013',
data: [(Math.random() * 100) + 1, (Math.random() * 100) + 1,
(Math.random() * 100) + 1, (Math.random() * 100) + 1,
(Math.random() * 100) + 1, (Math.random() * 100) + 1,
(Math.random() * 100) + 1, (Math.random() * 100) + 1,
(Math.random() * 100) + 1, (Math.random() * 100) + 1,
(Math.random() * 100) + 1, (Math.random() * 100) + 1,
(Math.random() * 100) + 1]
};
//alert('About to load the chart Data');
chart.addSeries({
name: JSONData.seriesName,
data: JSONData.data,
type: 'spline'
}, true);
chart.redraw(true);
};
// Create the chart
$('#container').highcharts({
chart: {
events: {
load: function () {
fnFetchData(this);
}
}
},
title: {
text: 'Chart Data from load Event'
},
exporting: {
enabled: true,
scale: 2,
filename: 'ChartWithDoubleupSeries'
},
spline: {
animation: false
},
series: []
});
});
I can reproduce the issue, here is an example:
http://jsfiddle.net/FtjJF/
I keep thinking it might be a bug that the 'load' event is being executed when exporting unless this is the standard behaviour and I need to include and extra option.
Versions details:
Highstock JS v1.3.1 (2013-04-09)
Highstock JS v1.3.1 (2013-04-09) Exporting module
Export chart through pragmatically like , make load event null
var chart = $('#chart-container').highcharts();
chart.exportChart(
{type: 'image/png', filename: 'name'},
{chart: { events:null } });
Simple workaround is to use setTimeout():
load: function () {
var chart = this;
setTimeout(function() {
fnFetchData(chart);
}, 1);
}
And for me looks like a bug, reported: https://github.com/highslide-software/highcharts.com/issues/1868
Thanks!
Currently as an alternative solution was to store the series names into an array. When the load event is fired I check the series name to see if it exists in the array. If it does, don't call the addSeries method.
var AvailableSeries = [];
$( chart.series).each(function(key,value){
AvailableSeries.push(value.name);
});
if ($.inArray( 'Sales 2013' ,AvailableSeries)==-1 ) {
chart.addSeries(name:'Sales 2013', data:[234,453,56732,435,4,45,32,232,43]});
};
After a little bit more investigation for a long term solution, I've added this line "options.chart.events = null;" to the exportpting.js script.
The problem with this solution might be a legitimate reason for the chart's events to be executed when the chart is being exported. I can't think of one. May be there should be an option to suppress events when exporting:
{ exporting:{ skipEvents:true } }

Allow setting of jquery ui slider to above max value or below the min value

I have a jquery ui slider that is linked to a numerical textbox. The slider has a max and min value.
See ui slider with text box input or
Using knockout js with jquery ui sliders for a knockout js implementation.
My question is: is it possible to set the value of the slider to above the max or below the min?
If value is outside the range it is set to the max or min of the range:
$(element).slider("value", value);
So for example, say the slider represents the percentage of your monthly salary between 50 and 100. Monthly salary is set to 10000. If you slide the slider it will vary from 5000 to 10000, but I still want users to be able to input values outside of the range. So if the user inputs 12000 the slider will slide to max, and if the user inputs 2000 the slider will slide to the min.
You can accomplish this by overriding the _trimAlignValue function that I noted in my comment:
$.ui.slider.prototype._trimAlignValue = function (val) {
var step = (this.options.step > 0) ? this.options.step : 1,
valModStep = val % step,
alignValue = val - valModStep;
if (Math.abs(valModStep) * 2 >= step) {
alignValue += (valModStep > 0) ? step : (-step);
}
return parseFloat(alignValue.toFixed(5));
};
This will effect every slider on the page--if this isn't the desired effect you should wrap this functionality in your own plugin (I can provide an example that does that too, if need be).
This combined with your existing KnockoutJS custom binding seems to work well.
Example: http://jsfiddle.net/Aa5nK/7/
Adapted from the answer in Using knockout js with jquery ui sliders
<h2>Slider Demo</h2>
Savings:
<input data-bind="value: savings, valueUpdate: 'afterkeydown'"
/>
<div style="margin: 10px" data-bind="slider: savings, sliderOptions: {min: 0, max: 100, range: 'min', step: 1}"></div>
And here's the custom binding:
ko.bindingHandlers.slider = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().sliderOptions || {};
$(element).slider(options);
ko.utils.registerEventHandler(element, "slidechange", function (event, ui) {
var value = valueAccessor();
if(!(value < $(element).slider('option', 'min') || value > $(element).slider('option', 'max')))
{
valueAccessor(ui.value);
}
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).slider("destroy");
});
ko.utils.registerEventHandler(element, "slide", function (event, ui) {
var observable = valueAccessor();
observable(ui.value);
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (isNaN(value)) value = 0;
if (value < $(element).slider('option', 'min')) {
value = $(element).slider("option", "min");
} else if (value > $(element).slider('option', 'max')) {
value = $(element).slider("option", "max");
}
$(element).slider("value", value);
}
};
var ViewModel = function () {
var self = this;
self.savings = ko.observable(10);
self.spent = ko.observable(5);
self.net = ko.computed(function () {
return self.savings() - self.spent();
});
};
ko.applyBindings(new ViewModel());
I adapted the jsFiddle from that answer too.

jQuery UI multiple selectable tooltips are collapsing

I'm new to jQuery UI.
I'm trying to create a selectable jQuery UI tooltip. The tooltip is associated with the links on a page.
When the link is surrounded by just text, it works fine. But when there are few links next to each other, the functionality overlaps and tooltips don't show smoothly anymore.
you can find the code on http://jsfiddle.net/zumot/Hc3FK/2/
Below the JavaScript code
$("[title][data-scan]").bind("mouseleave", function (event) {
event.stopImmediatePropagation();
var fixed = setTimeout('$("[title][data-scan]").tooltip("close")', 100);
$(".ui-tooltip").click(function () {
alert("I am clickable");
return false;
});
$(".ui-tooltip").hover(
function () {
clearTimeout(fixed);
},
function () {
$("[title][data-scan]").tooltip("close");
});}).tooltip({
items: "img, [data-scan], [title]",
content: function () {
var element = $(this);
if (element.is("[data-scan]")) {
var text = element.attr("href");
return "<a href='http://www.google.com'>You are trying to open a tooltip <span>" + text + "</span></a>";
}
if (element.is("[title]")) {
return element.attr("title");
}
if (element.is("img")) {
return element.attr("alt");
}
},
position: {
my: "right center",
at: "left center",
delay: 200,
using: function (position, feedback) {
$(this).css(position);
$("<div>")
.addClass(feedback.vertical)
.addClass(feedback.horizontal)
.appendTo(this);
}
}});
My attempt to fix the issue was by making the variable fixed global (to make it accessible by other jQuery UI properties), and on Open event, hide any other previously opened tooltips and clear the timeout id saved in fixed variable.
You can find the solution here http://jsfiddle.net/zumot/dVGWB/
, though to see the code working properly, you'll have to run it directly on your browser.
Here's the snapshort of the fixed code.
// Make the timeout id variable global
var fixed = 0;
$("[title][data-scan]").tooltip({
items: "img, [data-scan], [title]",
content: function () {
var element = $(this);
if (element.is("[data-scan]")) {
var text = element.attr("href");
return "<a href='http://www.google.com'>You are trying to open a tooltip <span>" + text + "</span></a>";
}
if (element.is("[title]")) {
return element.attr("title");
}
if (element.is("img")) {
return element.attr("alt");
}
},
open: function (event, ui) {
// When opening a new div, hide any previously opened tooltips first.
$(".ui-tooltip:not([id=" + ui.tooltip[0].id + "])").hide();
// clear timeout as well if there's any.
if (tf > 0) {
clearTimeout(tf)
};
},
position: {
my: "right center",
at: "left center",
delay: 200,
using: function (position, feedback) {
$(this).css(position);
$("<div>")
.addClass(feedback.vertical)
.addClass(feedback.horizontal)
.appendTo(this);
}
}
}).bind("mouseleave", function (event) {
// stop defeulat behaviour
event.stopImmediatePropagation();
fixed = setTimeout('$("[title][data-scan]").tooltip("close")', 100);
$(".ui-tooltip").hover(
function () {
clearTimeout(tf);
}, function () {
$("[title][data-scan]").tooltip("close");
})
});

Resources