How to watch styles with Knockout - jquery-ui

What I want to do is track the (top, left, width, height, zIndex) style attributes. But since the element that holds these styles doesn't exist until after third party code gets pulled in, i can't do it the normal way.
Here's what I've got thus far:
my.Module = function (module) {
return {
top: ko.observable(200);,
left: ko.observable(100);,
width: ko.observable(100);,
height: ko.observable(100);,
zIndex: ko.observable(0);,
};
};
This would be the normal binding context:
<div data-bind="style:{width:width, top:top, left:left, height:height, zindex:zIndex}"></div>
But the third party stuff ends up wrapping the div i pass it within another, and this is something I cannot change. So instead I've started creating a custom bindinghandler:
ko.bindingHandlers.Window = {
init: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
//Do third party creation stuff here...
ko.applyBindingsToNode(elemToActuallyWatch, {
style: {
width: value.width() + 'px',
height: value.height() + 'px',
top: value.top() + 'px',
left: value.left() + 'px',
zIndex: value.zIndex()
}
});
},
Now this applies the above styles to the appropriate div, but i still need the above values to change when both the data changes and when the element changes.
I feel like the 'px' that i have to tack on the ends shouldn't be needed but that was the only way i could get them to apply.
Is there a way from this point on, that lets say I'm using jqueryUI draggable and resizable, and a user moves/resizes the element that the values will automatically update the viewmodel data? Of course a non jquery specific answer would be best.

style is a one-way binding, from model to view. It won't update the model from the view. You'll probably need to use the resize option and update the model from there.

Related

How to change the default position of button tooltips in CKEditor 5

Having a small issue with tooltips in the editor, read the api but can't understand what it is saying and I can't seem to find examples anywhere that I can understand either.
I have set up a Classic Editor build, and all the buttons on the toolbar have tooltips with the default position below the button, I want to be able, just for this one instance of the editor, to change the tooltip position to above the buttons instead. The instance is set up like this:
ClassicEditor.create( document.querySelector( '#content' ) )
.then( editor => {
console.log( 'Editor was initialized', editor );
this.annEditorInstance = editor;
} )
.catch( err => {
console.error( err.stack );
} );
That creates an editor instance that is set up exactly as I want, except for the issue with the tooltip. How do I change this? Thanks in advance.
There are two approaches to the problem:
CSS
Tooltips elements have either .ck-tooltip_s or .ck-tooltip_n class. By default all CKEditor 5 tooltips have the former so you could override it in your styles and make it act like the later:
<style>
.ck.ck-tooltip.ck-tooltip_s {
bottom: auto;
top: calc(-1 * var(--ck-tooltip-arrow-size));
transform: translateY( -100% );
}
.ck.ck-tooltip.ck-tooltip_s .ck-tooltip__text::after {
top: auto;
bottom: calc(-1 * var(--ck-tooltip-arrow-size));
transform: translateX( -50% );
border-color: var(--ck-color-tooltip-background) transparent transparent transparent;
border-width: var(--ck-tooltip-arrow-size) var(--ck-tooltip-arrow-size) 0 var(--ck-tooltip-arrow-size);
}
</style>
JS
The UI of the editor is an MVC(VM) structure. The position of the tooltip can be controlled using the JS and the Button#tooltipPosition property ('s' or 'n').
E.g. you can access the toolbar UI elements using editor.ui.view.toolbar and change their properties:
editor.ui.view.toolbar.items.map( item => item.tooltipPosition = 'n' )
but note that not all toolbar items are buttons. Some, for instance, are dropdowns so you'd need to use item.buttonView.tooltipPosition = 'n' in that case. So unless you really want to use JS, I'd go with a simple CSS solution.

Trigger resizable in jasmine [duplicate]

I have a div element which is made jquery Resizable. It has alsoResize option set, so other elements resize simultaneously.
What I want to do, is to set size of this Resizable div element programmatically in such way, that all Resizable logic is triggered (especially this alsoResize option is taken into account).
How can I achieve that?
Update: It looks like the internals of jQuery UI have changed dramatically since I answered this and firing the event no longer works.
There's no direct way to fire the event anymore because the resizable plugin has been fundamentally changed. It resizes as the mouse is dragged rather than syncing items up at the end. This happens by it listening for the internal resize propagation event for resizable plugins which is now fired by the _mouseDrag handler. But it depends on variables set along the way, so just firing that even internally won't help.
This means even overriding it is messy at best. I'd recommend just manually resizing the alsoResize elements directly, independent of the UI widget altogether if that's possible.
But for fun let's say it isn't. The problem is that the internals of the plugin set various properties relating to previous and current mouse position in order to know how much to resize by. We can abuse use that to add a method to the widget, like this:
$.widget("ui.resizable", $.ui.resizable, {
resizeTo: function(newSize) {
var start = new $.Event("mousedown", { pageX: 0, pageY: 0 });
this._mouseStart(start);
this.axis = 'se';
var end = new $.Event("mouseup", {
pageX: newSize.width - this.originalSize.width,
pageY: newSize.height - this.originalSize.height
});
this._mouseDrag(end);
this._mouseStop(end);
}
});
This is just creating the mouse events that the resizable widget is looking for and firing those. If you wanted to do something like resizeBy it'd be an even simpler end since all we care about is the delta:
var end = $.Event("mouseup", { pageX: newSize.width, pageY: newSize.height });
You'd call the $.widget() method after jQuery UI and before creating your .resizable() instances and they'll all have a resizeTo method. That part doesn't change, it's just:
$(".selector").resizable({ alsoResize: ".other-selector" });
Then to resize, you'd call that new resizeTo method like this:
$(".selector").resizable("resizeTo", { height: 100, width: 200 });
This would act as if you instantly dragged it to that size. There are of course a few gotchas here:
The "se" axis is assuming you want resize by the bottom right - I picked this because it's by far the most common scenario, but you could just make it a parameter.
We're hooking into the internal events a bit, but I'm intentionally using as few internal implementation details as possible, so that this is less likely to break in the future.
It could absolutely break in future versions of jQuery UI, I've only tried to minimize the chances of that.
You can play with it in action with a fiddle here and the resizeBy version here.
Original answer:
You can do this:
$(".selector").trigger("resize");
alsoResize internally rigs up a handler to the resize event, so you just need to invoke that :)
You can trigger the bars programmatically. For example, to trigger the east-west resize event:
var elem =... // Your ui-resizable element
var eastbar = elem.find(".ui-resizable-handle.ui-resizable-e").first();
var pageX = eastbar.offset().left;
var pageY = eastbar.offset().top;
(eastbar.trigger("mouseover")
.trigger({ type: "mousedown", which: 1, pageX: pageX, pageY: pageY })
.trigger({ type: "mousemove", which: 1, pageX: pageX - 1, pageY: pageY })
.trigger({ type: "mousemove", which: 1, pageX: pageX, pageY: pageY })
.trigger({ type: "mouseup", which: 1, pageX: pageX, pageY: pageY }));
I am doing a 1px left followed by 1px right movement on the east bar handle.
To perform a full size, you can target .ui-resizable-handle.ui-resizable-se if you have east and south resize bars.
I needed the same thing for tests. Similar questions have only one promising answer https://stackoverflow.com/a/17099382/1235394, but it requires additional setup, so I ended with my own solution.
I have an element with resizable right edge
$nameHeader.resizable({handles: 'e', ... });
and I needed to trigger all callbacks during the test in order to resize all elements properly. The key part of test code:
var $nameHeader = $list.find('.list-header .name'),
$nameCell = $list.find('.list-body .name');
ok($nameHeader.hasClass('ui-resizable'), 'Name header should be resizable');
equal($nameCell.outerWidth(), 300, 'Initial width of Name column');
// retrieve instance of resizable widget
var instance = $nameHeader.data('ui-resizable'),
position = $nameHeader.position(),
width = $nameHeader.outerWidth();
ok(instance, 'Instance of resizable widget should exist');
// mouseover initializes instance.axis to 'e'
instance._handles.trigger('mouseover');
// start dragging, fires `start` callback
instance._mouseStart({pageX: position.left + width, pageY: position.top});
// drag 50px to the right, fires `resize` callback
instance._mouseDrag({pageX: position.left + width + 50, pageY: position.top});
// stop dragging, fires `stop` callback
instance._mouseStop({pageX: position.left + width + 50, pageY: position.top});
// ensure width of linked element is changed after resizing
equal($nameCell.outerWidth(), 350, 'Name column width should change');
Of course this code is brittle and may break when widget implementation changes.
Hack Disclaimer (tested on jQuery 1.12.4):
This basically waits for the dialog to be opened and then increments by 1px (which forces the resize() event) and then decrements by 1px (to regain original size)
just say this in the dialog open event handler:
$(this)
.dialog("option","width",$(this).dialog("option","width")+1)
.dialog("option","width",$(this).dialog("option","width")-1);
note:
This may not work with show effects (like fadeIn,slideDown etc) as the "resizer" code executes before the dialog is fully rendered.
$(".yourWindow").each(function(e) {
$(this).height($(this).find(".yourContent").height());
});
And the same with the width.

add textfield on clicking a button in sproutcore

How to add more textfields in a view on clicking a button in the same view in sproutcore?
I have a sliding pane with particular number of textfields. On clicking a button, I need to add more number of text field in the same view.
Or,
I should be able to select the number from a select button view and show those many number of textfield in the same view.
I would recommend using a SC.ListView for this purpose.
You should have a SC.ArrayController whose content is an array containing objects that represent each text field. This may be as simple as something like this:
MyApp.myController = SC.ArrayController.create({
content: [
SC.Object.create({ someProperty: "Text field value 1" }),
SC.Object.create({ someProperty: "Text field value 2" }),
SC.Object.create({ someProperty: "Text field value 3" })
]
});
Next, you'll create your SC.ListView and bind it's content to the controller, and create the exampleView whose content is bound to the someProperty property of the objects:
MyApp.MyView = SC.View.extend({
childViews: 'scrollView addButtonView'.w(),
scrollView: SC.ScrollView.extend({
layout: { top: 0, left: 0, right: 0, bottom: 50 },
contentView: SC.ListView.extend({
contentBinding: 'MyApp.myController.arrangedObjects',
rowHeight: 40,
exampleView: SC.View.extend({
childViews: 'textFieldView'.w(),
textFieldView: SC.TextFieldView.extend({
// Add a little margin so it looks nice
layout: { left: 5, top: 5, right: 5, bottom: 5 },
valueBinding: 'parentView.content.someProperty'
})
})
})
}),
addButtonView: SC.ButtonView.extend({
layout: { centerX: 0, bottom: 10, width: 125, height: 24 },
title: "Add Text Field",
// NOTE: The following really should be handled by a statechart
// action; I have done it inline for simplicity.
action: function() {
MyApp.myController.pushObject(SC.Object.create({ value: "New Field" }));
}
})
});
Now, when you click the "Add Text Field" button, it will add a new object to the controller array which will automatically re-render the list view with the new object and hence, the new text field.
A couple of notes:
This uses a SC.ScrollView in conjunction with the SC.ListView, you will almost always want to do it this way.
Since we are using standard bindings (not SC.Binding.oneWay()), editing the text fields will automatically update the someProperty property in the objects in MyApp.myController and vice versa: if you update the value via some other means, the text field should automatically update as well.
This should not be used for a large list as using the childViews method of view layout can be slow. If you need performance, you should change the exampleView to a view that overrides the render() method and manually renders a text input and sets up the proper change events and bindings.
Lastly, I can't remember if the proper syntax for the text field's valueBinding is parentView.content.someProperty or .parentView.content.someProperty (notice the period at the beginning). If the first way doesn't work, try adding the . and see if that works.
Like Topher I'm assuming you're using SproutCore not Ember (formerly SC2).
If you need to add an arbitrary child view to an arbitrary location on your view, you want view.appendChild. In the button's action, you would do something like this:
this.get('parentView').appendChild(SC.View.create({ ... }))
If you go this route, you'll have to figure out the layout for the new view yourself. If you don't need to precisely control the layout, then go with Topher's solution - the ListView does the layout piece for you.

HighCharts Keep Vertical Line on Click Event

Using this example: http://jsfiddle.net/gh/get/jquery/1.7.2/highslide-software/highcharts.com/tree/master/samples/stock/demo/candlestick-and-volume/
When you hover over points on the chart, you get a nice vertical line showing you which point you're currently on. I want to modify the click event so that the vertical line stays when I hover away after a click. Changing the line color would be ideal on click, but not necessary.
If I click another point I'd want to remove any previous lines. Any ideas on how I could accomplish this?
The above solution like I said, is really cool, but is kind of a hack (getting the path of the crosshair) into the implementation details of highcharts, and may stop working in future releases, may not be totally cross browser (esp since <IE8 do not support SVG, the adding path may still work as it should be handled by highchart's add path method, but getting the crosshair's path may not work, I may be wrong, am an SVG noob). So here I give you the alternate solution of dynamically adding plotLines. PlotLines also allow some additional features like dashStyles, label etc.
get the axis and x value of point clicked (may not exactly overlap the crosshair)
var xValue = evt.xAxis[0].value;
var xAxis = evt.xAxis[0].axis;
Or
EDIT If you want to have the plotLine at the location of the crosshair and not the click position, you can use following formula (No direct API to get this, obtained from source code hence may stop working if code changes)
var chart = this;
var index = chart.inverted ? chart.plotHeight + chart.plotTop - evt.chartY : evt.chartX - chart.plotLeft;
var xValue = chart.series[0].tooltipPoints[index].x;
Add plotline
xAxis.addPlotLine({
value: xValue,
width: 1,
color: 'red',
//dashStyle: 'dash',
id: myPlotLineId
});
You can cleanup existing plotline
$.each(xAxis.plotLinesAndBands,function(){
if(this.id===myPlotLineId)
{
this.destroy();
}
});
OR
try {
xAxis.removePlotLine(myPlotLineId);
} catch (err) {}
Putting the pieces together
var myPlotLineId="myPlotLine";
...
var chart=this;
index = chart.inverted ? chart.plotHeight + chart.plotTop - evt.chartY : evt.chartX - chart.plotLeft;
var xValue = chart.series[0].tooltipPoints[index];
// var xValue = evt.xAxis[0].value; // To use mouse position and not crosshair's position
var xAxis = evt.xAxis[0].axis;
$.each(xAxis.plotLinesAndBands,function(){
if(this.id===myPlotLineId)
{
this.destroy();
}
});
xAxis.addPlotLine({
value: xValue,
width: 1,
color: 'red',
//dashStyle: 'dash',
id: myPlotLineId
});
...
Add plot lines at click position # jsFiddle
Persist crosshair/cursor as plot lines on click # jsFiddle
You can do it in several ways
Highchart has a very cool renderer that allows you to add various graphics to the chart. One of the options is to add a path I will be illustrating the same here.
We shall reuse the path of the crosshair and add the same to the chart with some additional styles like color you mentioned. The path of the crosshair can be optained as this.tooltip.crosshairs[0].d this is in string form and can be easily converted to an array using the Array.split() function
click: function() {
this.renderer.path(this.tooltip.crosshairs[0].d.split(" ")).attr({
'stroke-width': 2,
stroke: 'red'
}).add();
}
This will accomplish adding the line. You can store the returned object into a global variable and then when you are about to add another such line, you can destroy the existing one by calling Element.destroy()
var line;
...
chart:{
events: {
click: function() {
if (line) {
line.destroy();
}
line = this.renderer.path(this.tooltip.crosshairs[0].d.split(" ")).attr({
'stroke-width': 2,
stroke: 'red'
}).add();
}
}
...
Persist tooltip / crosshair on click # jsFiddle
Assuming you don't have much meta data to be shown along with the line, this is the easiest (or the coolest :) ) approach. You can also attach meta data if you want to using the renderer's text object etc.
An alternate way could be adding vertical plotLines to the xAxis
UPDATE
Refer my other solution to this question, that would work with zoom,scroll,etc

jquery ui-effects-wrapper causing havoc

I am using jquery-ui and at some point I use the show and hide functions quite heavily to animate changing images coming in and out.
From some reason, after a few tries all of a sudden the controls on my page stop responding to clicks. After a bit of poking arround using firebug I discovered my page is filled with div's of the class ui-effects-wrapper.
I have no idea why this happens or how to stop it. If I remove these divs I can no longer see the images I've been animating.
Any ideas?
ui-effects-wrapper was added by jquery UI effect plugin.
Here is some code taken from jquery.effects.core.js:
// Wraps the element around a wrapper that copies position properties
createWrapper: function(element) {
// if the element is already wrapped, return it
if (element.parent().is('.ui-effects-wrapper')) {
return element.parent();
}
// wrap the element
var props = {
width: element.outerWidth(true),
height: element.outerHeight(true),
'float': element.css('float')
},
wrapper = $('<div></div>')
.addClass('ui-effects-wrapper')
.css({
fontSize: '100%',
background: 'transparent',
border: 'none',
margin: 0,
padding: 0
});
element.wrap(wrapper);
wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element
// transfer positioning properties to the wrapper
if (element.css('position') == 'static') {
wrapper.css({ position: 'relative' });
element.css({ position: 'relative' });
} else {
$.extend(props, {
position: element.css('position'),
zIndex: element.css('z-index')
});
$.each(['top', 'left', 'bottom', 'right'], function(i, pos) {
props[pos] = element.css(pos);
if (isNaN(parseInt(props[pos], 10))) {
props[pos] = 'auto';
}
});
element.css({position: 'relative', top: 0, left: 0, right: 'auto', bottom: 'auto' });
}
return wrapper.css(props).show();
},
In one function, I had some code that toggled the visibility of two buttons and employed effects such as fadeTo and bounce. Occasionally there would be a wrapper remaining.
I tried adding this to my method, the idea being that it removes the wrappers 1.1 seconds after the effects have been queued up.
setTimeout('$(".ui-effects-wrapper").children().unwrap();', 1100);
This removes the stale wrappers, but there is the chance that it can also remove subsequent wrappers that are being used for effects.
I'd be very interested in seeing any improvements on this technique.

Resources