How to make window size observable using knockout - javascript

Trying to do something about the browser window:
Is it possible to make the window size ($(window).width(), $(window).height()) observable using Knockout?
How to keep FUTURE Added Elements in the center of the window? Is there something can be done using the jquery live method or the knockout custom bindings?
Any Suggestion appreciated!

The only way to make them observable is to proxy them into observable properties.
var yourViewModel = {
width: ko.observable(),
height: ko.observable()
};
var $window = $(window);
$window.resize(function () {
yourViewModel.width($window.width());
yourViewModel.height($window.height());
});
I don't really understand your second question. Couldn't you just use css for this?
EDIT
Second question. One possibility would be write a binding handler to do this (untested).
ko.bindingHandlers.center {
init: function (element) {
var $el = $(element);
$el.css({
left: "50%",
position: "absolute",
marginLeft: ($el.width() / 2) + 'px'
});
}
}
The 50% and margin-left is a great way to center things in your scenarios since it automatcially works even if the window is resized. Obviously if the divs themselves resize you need to recalculate the left margin, this could always be bound to a value on the viewmodel.
Hope this helps.

To elaborate on Dean's comment of his winsize custom bindingHandler, this is how it can be used:
JS
ko.bindingHandlers.winsize = {
init: function (element, valueAccessor) {
$(window).resize(function () {
var value = valueAccessor();
value({ width: $(window).width(), height: $(window).height() });
});
}
}
self.windowSize = ko.observable();
HTML
<div data-bind="winsize: windowSize"></div>

Related

Retaining "height" declaration in jQuery after an Ajax call (dropdown selection loads product data, resets height on other page elements)

I have some jQuery setting a height on product descriptions, this can be seen here:
https://www.gabbyandlaird.com/product/truition-products/truition-healthy-weight-loss-pack
The code I'm using to achieve this is:
$(document).ready(function() {
var $dscr = $('.commerce-product-field-field-long-description'),
$switch = $('#toggle'),
$initHeight = 70; // Initial height
$dscr.each(function() {
$.data(this, "realHeight", $(this).height()); // Create new property realHeight
}).css({ overflow: "hidden", height: $initHeight + 'px' });
$switch.toggle(function() {
$dscr.animate({ height: $dscr.data("realHeight") }, 200);
$switch.html("- Read More").toggleClass('toggled');
}, function() {
$dscr.animate({ height: $initHeight}, 200);
$switch.html("+ Read More").toggleClass();
});
});
I am by no means a Javascript expert (or even an amateur for that matter). My knowledge of jQuery is fairly limited, so I'm not sure what needs to be changed above so that when the dropdown selection reloads the data on the page, I don't lose the height value on the "Read More" section of the product description. Any assistance is greatly appreciated. Thanks!
as per your code above it looks you are doing everything when the document is ready.
here is what you can do.
var initialHeight = function(){
// you height initialization code here
}
$(function () {
$(document).ready(initialHeight); // initializes the height on document ready
$("#yourSelectBox").change(initialHeight); // initializes the height on combobox selection change
});
hope this helps.

jQuery height() function not working on element

I am currently in the process of putting together a jQuery touch carousel. I seem to have run into a slight problem when setting the height (or width) of an element, however.
Below is the relevant code from the function:
carousel = function( container, options )
{
this.settings = $.extend({
// General settings:
width: 600,
height: 400,
slides: 'div',
snapTo: true,
mouseSupport: true,
trackMovement: true,
slideWidth: 600,
slideHeight: 400,
// CSS classes:
wrapperCls: 'carousel_wrapper',
innerCls: 'carousel_scroll_inner'
}, options);
this.container = container;
this.scroller = null;
this.slides = this.container.children(this.settings.slides);
this.numSlides = this.slides.length;
this.scrollWidth = this.settings.width * this.numSlides;
this.container.addClass(this.settings.wrapperCls);
this.scroller = $('<div />').addClass(this.settings.innerCls);
this.container.wrapInner(this.scroller);
this.scroller.width(1500)
this.scroller.css('width', this.scrollWidth);
this.container.css({ width: this.settings.width, height: this.settings.height });
};
This is in turn called by doing something like:
$(function() {
var carousel1 = new carousel($('#myElement'));
});
#myElement definitely exists, and no error is thrown. this.scroller also contains the correct jQuery object which has been wrapped to the contents of this.container. The problem is that I am unable to set the CSS width or height of this.scroller, using either the width(), height() or css() functions.
What am I missing? I have put together a jsFiddle to demonstrate the problem. If you check the element inspector, you can see that the dimensions are never updated for the div.carousel_scroll_inner element.
The problem is that, here:
this.container.wrapInner(this.scroller);
jQuery will use a copy. So this.scroller is not the same element wrapped around whatever is below this.container. Try:
this.container.addClass(this.settings.wrapperCls);
this.scroller = $('<div />').addClass(this.settings.innerCls);
this.scroller.append(this.container.contents());
this.container.append(this.scroller);

How to data bind to the CSS top, left, width, and height of a DOM element?

I'm trying to use KnockoutJS to build an interactive dashboard with data bindings on different aspects of the style property; namely left, top, width, and height. For example, I'm using JQuery UI along with ui-draggable and ui-resizable effects to enable users to drag panels around on a canvas and resize them however they see fit. Without KnockoutJS, I was simply iterating over each div and stealing these properties from the dom element. I'm sure there's a better way using KnockoutJS, right?
To explain my problem a little better, consider two panels side by side:
<div id='dashboard'>
<div id='panel1' class='ui-draggable ui-resizable' data-bind='?????'>
<!-- content goes here -->
</div>
<div id='panel2' class='ui-draggable ui-resizable' data-bind='?????'>
<!-- content goes here -->
</div>
<button data-bind='click: send'>Update</button>
</div>
My view model looks something like this (note: pseudo coded for brevity):
var viewModel = {
panels: [
{ id: 'panel1', left: 0, top: 0, width: 50; height: 50 },
{ id: 'panel2', left: 50, top: 0, width: 50; height: 50 } ],
send: function() {
ko.utils.postJson( location.href , { panels: this.panels } );
}
};
ko.applyBindings( '#dashboard', viewModel );
I'd like to bind it in such a way that when the user sizes any of the div elements (ie. the panels), that it would then update the underlying viewModel using the built in data binding OnMouseUp. Then when the user clicks the "Update" button, I would then post the coordinates to the server.
I'm thinking it might have something to do with custom templates, but even there I ran into some complexities that I couldn't quite grasp in managing the mouse events on each panel.
<script type='text/html' id='panelTemplate'>
<div class='ui-draggable ui-resizable'
style='top: ${ top }; left: ${ left }; width: ${ width }px; height: ${ height }px;'>
<!-- content goes here -->
</div>
</script>
Ideas?
With time passing the way that it does I was not able to see Green's solution in JSFiddle and the solution from Awais seemed a little long so I slugged through it some more.
I found the following binding works for setting the width. The parens and the 'px' seem to be important. In my case 'AWidth' is the width of a div tag.
data-bind="style: {width: AWidth() + 'px'}"
I was having exactly the same problem: bind a KnockoutJS model to collection of draggable and resizable divs. The solution is to use custom binding and handle the event of end of resize and end of drag, as I explain here.
$(document).ready(function () {
$('#btnSave').click(function () {
$.ajax({
url: '/Save.ashx',
contentType: "application/json; charset=utf-8",
type: 'POST',
dataType: 'json',
data: ko.toJSON(viewModel),
error: function () { alert("ajax error"); }
});
});
ko.bindingHandlers.jqDraggableResizable = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var obj = valueAccessor();
var $elem = $(element);
element.dataObj = obj;
$elem.resizable({
stop: function (event, ui) {
this.dataObj.H(ui.size.height);
this.dataObj.W(ui.size.width);
}
});
$elem.draggable({
stop: function (event, ui) {
this.dataObj.X(ui.position.left);
this.dataObj.Y(ui.position.top);
}
});
}
}
ko.applyBindings(viewModel);
});
This question was answered over on the KnockoutJS Google Group by a brilliant developer named Green.
He demonstrated how to do this using a custom extension library (that he built) for KnockoutJS. His solution allow additional JQueryUI events to be "bindable" (ie. drag & resize). He then posted his solution along with his knockout-ext.js file out on JSFiddle for me to use and learn from it.
Thanks again, Green! :)
I was able to get it working with a simple solution, it builds off Ryans view.
This allows me to save my changes real time as I drag drop elements, with BreezeJS
in viewmodel
ko.bindingHandlers.draggable = {
init: bindDraggable
};
function bindDraggable(element, value) {
$(element).draggable({
stop: onStopDrag
});
function onStopDrag(event, ui) {
ko.dataFor(element).PositionX(ui.position.left);
ko.dataFor(element).PositionY(ui.position.top);
datacontext.saveChanges(); //if you want real time updating on drop
}
}
and in my view
<div data-bind="foreach: posts">
<div class="BoardPost" data-bind="style: { top: PositionY() + 'px', left: PositionX() + 'px' }, draggable:null">
<div class="Title" data-bind="text: Content"></div>
</div>

Todays Special: How is this done? Is there a jquery plugin or product to do it?

I saw this technique at the bottom of a web page where the TAB stays in place at the bottom of the page and can be opened and closed to display more info. I assume it can be rotated to display a different special for different days. Can you point me to anything like it or explain the technique ? thanks. Here is a sample: http://www.tmdhosting.com/ look at the bottom of the page .
position: fixed is how you manage to keep something at the bottom or top of the page, regardless of scrolling.
This is easily discoverable using firebug's (http://getfirebug.com/) inspect element feature
You can check out my version of this at uxspoke.com
I wrote a jQuery plugin to do it, and calling it is straightforward:
$('#about').pulloutPanel({open:true}).
click(function() { $(this).trigger('toggle'); }) });
I basically instrument the panel to support "open", "close" events, and the implement the appropriate animations around them. The only "hard" part is getting the height right. It also supports "toggle" so you can add a generic click handler to it to open or close it. Finally, it uses opened/closed classes to keep track of its current state. That's it!
The code's pretty coupled to the technologies on the page (Csster) and the design it is in, so I'm not sure it will work for you. You can either use Csster, or just put the CSS rules into your stylesheet and remove them from the code. The important Css attributes are the positioning and bottom.
Here it is:
$.fn.pulloutPanel = function(options) {
var settings = $.extend({}, {
attachTo: 'bottom',
css: {
left: 0,
minHeight: 390,
border: '1px 1px 1px 0 solid #666',
has: [roundedCorners('tr', 10),boxShadow([0,0], 10, phaseToColor('requirements').saturate(-30).darken(50))],
cursor: 'pointer'
}, options);
return $(this).each(function() {
var $this = $(this);
$this.addClass('pullout_panel');
$this.bind('open', function(event) {
$this.animate({bottom: 0}, 'slow', 'easeOutBounce', function() {
$this.removeClass('closed').addClass('opened');
$this.trigger('opened');
});
});
$this.bind('close', function(event) {
var height = $this.innerHeight();
$this.animate({bottom: -height + 50}, 'slow', 'easeOutBounce', function() {
$this.addClass('closed').removeClass('opened');
$this.trigger('closed');
});
});
$this.bind('toggle', function(event) {
$this.trigger($this.hasClass('opened') ? 'close' : 'open');
});
once(function() {
Csster.style({
'.pullout_panel': {
position: 'fixed',
bottom: 0,
has: [settings.css]
}
});
});
$this.trigger(settings.open ? 'open' : 'close');
});
};

Shade entire page, unshade selected elements on hover

I'm trying to make a page inspection tool, where:
The whole page is shaded
Hovered elements are unshaded.
Unlike a lightbox type app (which is similar), the hovered items should remain in place and (ideally) not be duplicated.
Originally, looking at the image lightbox implementations, I thought of appending an overlay to the document, then raising the z-index of elements upon hover. However this technique does not work in this case, as the overlay blocks additional mouse hovers:
$(function() {
window.alert('started');
$('<div id="overlay" />').hide().appendTo('body').fadeIn('slow');
$("p").hover(
function () {
$(this).css( {"z-index":5} );
},
function () {
$(this).css( {"z-index":0} );
}
);
Alternatively, JQueryTools has an 'expose' and 'mask' tool, which I have tried with the code below:
$(function() {
$("a").click(function() {
alert("Hello world!");
});
// Mask whole page
$(document).mask("#222");
// Mask and expose on however / unhover
$("p").hover(
function () {
$(this).expose();
},
function () {
$(this).mask();
}
);
});
Hovering does not work unless I disable the initial page masking. Any thoughts of how best to achieve this, with plain JQuery, JQuery tools expose, or some other technique? Thankyou!
What you can do is make a copy of the element and insert it back into the DOM outside of your overlay (with a higher z-index). You'll need to calculate its position to do so, but that's not too difficult.
Here is a working example.
In writing this I re-learned the fact that something with zero opacity cannot trigger an event. Therefore you can't use .fade(), you have to specifically set the opacity to a non-zero but very small number.
$(document).ready(function() { init() })
function init() {
$('.overlay').show()
$('.available').each(function() {
var newDiv = $('<div>').appendTo('body');
var myPos = $(this).position()
newDiv.addClass('available')
newDiv.addClass('peek')
newDiv.addClass('demoBorder')
newDiv.css('top',myPos.top+'px')
newDiv.css('left',myPos.left+'px')
newDiv.css('height',$(this).height()+'px')
newDiv.css('width',$(this).width()+'px')
newDiv.hover(function()
{newDiv.addClass('full');newDiv.stop();newDiv.fadeTo('fast',.9)},function()
{newDiv.removeClass('full');newDiv.fadeTo('fast',.1)})
})
}
Sorry for the prototype syntax, but this might give you a good idea.
function overlay() {
var div = document.createElement('div');
div.setStyle({
position: "absolute",
left: "0px",
right: "0px",
top: "0px",
bottom: "0px",
backgroundColor: "#000000",
opacity: "0.2",
zIndex: "20"
})
div.setAttribute('id','over');
$('body').insert(div);
}
$(document).observe('mousemove', function(e) {
var left = e.clientX,
top = e.clientY,
ele = document.elementFromPoint(left,top);
//from here you can create that empty div and insert this element in there
})
overlay();

Categories