I have implemented a slider for height in my application.
When I slide the min and max values, it will go up to 0.9 and on to 1.0 because it's a decimal value. I want it to go up to 0.11 and at 0.12 it will convert to 1.0, because I want height in feet and inches.(1 Feet = 12 inches).
I have implemented knockoutjs for it as below :
ko.bindingHandlers.TwoSideSlider = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().sliderOptions || {};
var sliderValues = ko.utils.unwrapObservable(valueAccessor());
if (sliderValues.min !== undefined) {
options.range = true;
}
options.slide = function (e, ui) {
if (sliderValues.min) {
console.log(ui.values[0]);
sliderValues.min(ui.values[0]);
sliderValues.max(ui.values[1]);
} else {
sliderValues.value(ui.value);
}
};
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).slider("destroy");
});
$(element).slider(options);
},
update: function (element, valueAccessor) {
var sliderValues = ko.toJS(valueAccessor());
if (sliderValues.min !== undefined) {
$(element).slider("values", [sliderValues.min, sliderValues.max]);
} else {
$(element).slider("value", sliderValues.value);
}
}
};
};
And my Html Code is :
<div class="slider-control">
<div data-bind="TwoSideSlider: { min: MinHeight, max: MaxHeight }, sliderOptions: {min: 0, max: 15, step: 0.1}"></div>
<div style="top:-12px;" data-bind="TwoSideSlider: { value: MaxHeight }, sliderOptions: {min: 0, max: 15, step: 0.1}"></div>
</div>
Can anyone advise how I can achieve this?
Decimal values are base 10, so you won't be able to do what you are trying to do without writing some custom code to convert decimal values to feet and inches.
So ideally, you need 1.5 to equal 1ft 6in.
If you use computed values based on an observable, you can just perform simple maths to convert the values for you when the slider moves. For example you can get feet and inches from 1.5 like so:
// Round 1.5 down to nearest whole number
Math.floor(1.5); // would return 1 (feet)
// Take the value after decimal point, multiply by 12 & round to a whole number
Math.round(((1.5) % 1) * 12); // would return 0.5 * 12 = 6 (inches)
Integrating that in to a computed is demonstrated on the snippet below:
ko.bindingHandlers.slider = {
init: function(element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().sliderOptions || {};
var observable = valueAccessor();
if (observable().splice) {
options.range = true;
}
options.slide = function(e, ui) {
observable(ui.values ? ui.values : ui.value);
};
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).slider("destroy");
});
$(element).slider(options);
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).slider(value.slice ? "values" : "value", value);
}
};
ViewModel = function() {
this.values = ko.observable([1.9, 15.0]);
this.valuesJson = ko.computed(function() {
return ko.toJSON({
values: this.values
});
}, this);
this.firstFeet = ko.computed(function() {
return Math.floor(this.values().slice(0, 1));
}, this);
this.firstInches = ko.computed(function() {
return Math.round(((this.values().slice(0, 1)) % 1) * 12);
}, this);
this.lastFeet = ko.computed(function() {
return Math.floor(this.values().slice(1, 2));
}, this);
this.lastInches = ko.computed(function() {
return Math.round(((this.values().slice(1, 2)) % 1) * 12);
}, this);
}
ko.applyBindings(new ViewModel());
body,
html {
font-family: Verdana;
font-size: 8pt;
}
.slider {
width: 60%;
margin: 20px auto;
}
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/themes/base/jquery-ui.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="slider" data-bind="slider: values, sliderOptions: {min: 1.0, max: 15.0, step: 0.05}"></div>
<span data-bind="text: valuesJson"></span>
<hr />
<p>Min: <span data-bind="text: firstFeet"></span>ft
<span data-bind="text: firstInches"></span>in</p>
<p>Max: <span data-bind="text: lastFeet"></span>ft
<span data-bind="text: lastInches"></span>in</p>
<hr />
<p>Min: <span data-bind="text: firstFeet"></span>.<span data-bind="text: firstInches"></span>
</p>
<p>Max: <span data-bind="text: lastFeet"></span>.<span data-bind="text: lastInches"></span>
</p>
Related
I'm quite new to JS/JQuery web dev in general.
For a work task I've been asked to come up with a solution where the first 3 funds in a list are shown and if they are invested in more than 3 then there will be a show all your funds button and once clicked it will toggle the rest of the funds.`
<!DOCTYPE html>
<html lang="en">
<head>
<title>Check your retirement plan demo</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="css/dropdown-component.css" />
<script src="js/modernizr.custom.63321.js"></script>
</head>
<body style="background-color:#eaeaea;">
<div class="container">
<h2> Dropdown Component</h2>
<!-- THIS BIT FUNDS START -->
<section class="main clearfix">
<select id="cd-dropdown" name="cd-dropdown" class="cd-select">
<option value="-1" selected>Funds in your portfolio (3)</option>
<option value="1">fund1 £60,000</option>
<option value="2">fund 2 £40,000</option>
<option value="3">fund 3 £24,000</option>
<option value="4">View all funds ></option>
<option value="5">Fund 5 £40,000</option>
</select>
</section>
<!-- THIS BIT FUNDS END -->
</div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery.dropdown.js"></script>
<script type="text/javascript">
$(function () {
$('#cd-dropdown').dropdown({
gutter: 5
});
});
</script>
</body>
</html>`
I'm using jquery dropdown solution to help me with the funds its not got the functionality to hide the funds greater than 3 and toggle them once the see more funds is clicked.
/**
* jquery.dropdown.js v1.0.0
* http://www.codrops.com
*
* Licensed under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright 2012, Codrops
* http://www.codrops.com
*/
; (function ($, window, undefined) {
'use strict';
$.DropDown = function (options, element) {
this.$el = $(element);
this._init(options);
};
// the options
$.DropDown.defaults = {
speed: 300,
easing: 'ease',
gutter: 0,
// initial stack effect
stack: true,
// delay between each option animation
delay: 0,
// random angle and positions for the options
random: false,
// rotated [right||left||false] : the options will be rotated to thr right side or left side.
// make sure to tune the transform origin in the stylesheet
rotated: false,
// effect to slide in the options. value is the margin to start with
slidingIn: false,
onOptionSelect: function (opt) { return false; }
};
$.DropDown.prototype = {
_init: function (options) {
// options
this.options = $.extend(true, {}, $.DropDown.defaults, options);
this._layout();
this._initEvents();
},
_layout: function () {
var self = this;
this.minZIndex = 1000;
var value = this._transformSelect();
this.opts = this.listopts.children('li');
this.optsCount = this.opts.length;
this.size = { width: this.dd.width(), height: this.dd.height() };
var elName = this.$el.attr('name'), elId = this.$el.attr('id'),
inputName = elName !== undefined ? elName : elId !== undefined ? elId : 'cd-dropdown-' + (new Date()).getTime();
this.inputEl = $('<input type="hidden" name="' + inputName + '" value="' + value + '"></input>').insertAfter(this.selectlabel);
this.selectlabel.css('z-index', this.minZIndex + this.optsCount);
this._positionOpts();
if (Modernizr.csstransitions) {
setTimeout(function () { self.opts.css('transition', 'all ' + self.options.speed + 'ms ' + self.options.easing); }, 25);
}
},
_transformSelect: function () {
var optshtml = '', selectlabel = '', value = -1;
this.$el.children('option').each(function () {
var $this = $(this),
val = isNaN($this.attr('value')) ? $this.attr('value') : Number($this.attr('value')),
classes = $this.attr('class'),
selected = $this.attr('selected'),
label = $this.text();
if (val !== -1) {
optshtml +=
classes !== undefined ?
'<li data-value="' + val + '"id=fundID' + val + '><span class="' + classes + '">' + label + '</span></li>' :
'<li data-value="' + val + '"id=fundID' + val + '><span>' + label + '</span></li>';
}
if (val > 3) {}
if (selected) {
selectlabel = label;
value = val;
}
});
this.listopts = $('<ul/>').append(optshtml);
this.selectlabel = $('<span/>').append(selectlabel);
this.dd = $('<div class="cd-dropdown"/>').append(this.selectlabel, this.listopts).insertAfter(this.$el);
this.$el.remove();
return value;
},
_positionOpts: function (anim) {
var self = this;
this.listopts.css('height', 'auto');
this.opts
.each(function (i) {
$(this).css({
zIndex: self.minZIndex + self.optsCount - 1 - i,
top: self.options.slidingIn ? (i + 1) * (self.size.height + self.options.gutter) : 0,
left: 0,
marginLeft: self.options.slidingIn ? i % 2 === 0 ? self.options.slidingIn : - self.options.slidingIn : 0,
opacity: self.options.slidingIn ? 0 : 1,
transform: 'none'
});
});
if (!this.options.slidingIn) {
this.opts
.eq(this.optsCount - 1)
.css({ top: this.options.stack ? 9 : 0, left: this.options.stack ? 4 : 0, width: this.options.stack ? this.size.width - 8 : this.size.width, transform: 'none' })
.end()
.eq(this.optsCount - 2)
.css({ top: this.options.stack ? 6 : 0, left: this.options.stack ? 2 : 0, width: this.options.stack ? this.size.width - 4 : this.size.width, transform: 'none' })
.end()
.eq(this.optsCount - 3)
.css({ top: this.options.stack ? 3 : 0, left: 0, transform: 'none' });
}
},
_initEvents: function () {
var self = this;
this.selectlabel.on('mousedown.dropdown', function (event) {
self.opened ? self.close() : self.open();
return false;
});
this.opts.on('click.dropdown', function () {
if (self.opened) {
var opt = $(this);
self.options.onOptionSelect(opt);
self.inputEl.val(opt.data('value'));
self.selectlabel.html(opt.html());
self.close();
}
});
},
open: function () {
var self = this;
this.dd.toggleClass('cd-active');
this.listopts.css('height', (this.optsCount + 1) * (this.size.height + this.options.gutter));
this.opts.each(function (i) {
$(this).css({
opacity: 1,
top: self.options.rotated ? self.size.height + self.options.gutter : (i + 1) * (self.size.height + self.options.gutter),
left: self.options.random ? Math.floor(Math.random() * 11 - 5) : 0,
width: self.size.width,
marginLeft: 0,
transform: self.options.random ?
'rotate(' + Math.floor(Math.random() * 11 - 5) + 'deg)' :
self.options.rotated ?
self.options.rotated === 'right' ?
'rotate(-' + (i * 5) + 'deg)' :
'rotate(' + (i * 5) + 'deg)'
: 'none',
transitionDelay: self.options.delay && Modernizr.csstransitions ? self.options.slidingIn ? (i * self.options.delay) + 'ms' : ((self.optsCount - 1 - i) * self.options.delay) + 'ms' : 0
});
});
this.opened = true;
},
close: function () {
var self = this;
this.dd.toggleClass('cd-active');
if (this.options.delay && Modernizr.csstransitions) {
this.opts.each(function (i) {
$(this).css({ 'transition-delay': self.options.slidingIn ? ((self.optsCount - 1 - i) * self.options.delay) + 'ms' : (i * self.options.delay) + 'ms' });
});
}
this._positionOpts(true);
this.opened = false;
}
}
$.fn.dropdown = function (options) {
var instance = $.data(this, 'dropdown');
if (typeof options === 'string') {
var args = Array.prototype.slice.call(arguments, 1);
this.each(function () {
instance[options].apply(instance, args);
});
}
else {
this.each(function () {
instance ? instance._init() : instance = $.data(this, 'dropdown', new $.DropDown(options, this));
});
}
return instance;
};
})(jQuery, window);'
Can anybody help me to either come up with a quick solution or point me in the correct direction? Thanks
example fiddle
You want to create a hidden element on the page that will cache or house the options that you do not want visible. Then, you would add some CSS to that element and use jQuery to place options into it.
HTML
<select id="cd-dropdown" name="cd-dropdown" class="cd-select">
<option value="-1" selected>Funds in your portfolio (3)</option>
<option value="1">fund1 £60,000</option>
<option value="2">fund 2 £40,000</option>
<option value="3">fund 3 £24,000</option>
<option value="4">View all funds ></option>
<option value="5">Fund 5 £40,000</option>
</select>
<input type="button" value="View all" class='view_more_button' />
<input type="button" value="View less" class='view_less_button hide' />
CSS
.hide {
left: -9999px !important;
position: absolute !important;
visibility: hidden;
z-index: -500;
top: -9999px;
}
JavaScript
//Create on DOM element dynamically that will house options greater than three.
var blank_input = $('<select>', {
class: 'hidden_input_field hide',
name: 'hidden_fields'
});
//This variable caches the master select element.
var master_select_menu = jQuery('#cd-dropdown');
//Put the blank select field in the DOM tree to be select via jQuery selectors.
blank_input.appendTo(jQuery('body'));
jQuery('.view_more_button').on('click', function(e) {
var place_holder_select_field = jQuery('.hidden_input_field');
if (place_holder_select_field.children('option').length) {
jQuery('.moved').each(function(index,element) {
jQuery(element).appendTo(master_select_menu);
});
jQuery(e.target).addClass('hide');
jQuery('.view_less_button').removeClass('hide');
}
master_select_menu.val(-1).change();
});
jQuery('.view_less_button').on('click', function(e) {
view_three();
jQuery(e.target).addClass('hide');
jQuery('.view_more_button').removeClass('hide');
master_select_menu.val(-1).change();
});
view_three();
//This function gets called as soon as the page loads, where it appends any option element greater than 3 to the hidden select field.
function view_three() {
jQuery('option').each(function(index, element) {
var place_holder_select_field = jQuery('.hidden_input_field');
if (index > 3) {
jQuery(element).addClass('moved').appendTo(place_holder_select_field);
}
});
}
//Create on DOM element dynamically that will house options greater than three.
var blank_input = $('<select>', {
class: 'hidden_input_field hide',
name: 'hidden_fields'
});
//This variable caches the master select element.
var master_select_menu = jQuery('#cd-dropdown');
//Put the blank select field in the DOM tree to be select via jQuery selectors.
blank_input.appendTo(jQuery('body'));
jQuery('.view_more_button').on('click', function(e) {
var place_holder_select_field = jQuery('.hidden_input_field');
if (place_holder_select_field.children('option').length) {
jQuery('.moved').each(function(index,element) {
jQuery(element).appendTo(master_select_menu);
});
jQuery(e.target).addClass('hide');
jQuery('.view_less_button').removeClass('hide');
}
master_select_menu.val(-1).change();
});
jQuery('.view_less_button').on('click', function(e) {
view_three();
jQuery(e.target).addClass('hide');
jQuery('.view_more_button').removeClass('hide');
master_select_menu.val(-1).change();
});
view_three();
//This function gets called as soon as the page loads, where it appends any option element greater than 3 to the hidden select field.
function view_three() {
jQuery('option').each(function(index, element) {
var place_holder_select_field = jQuery('.hidden_input_field');
if (index > 3) {
jQuery(element).addClass('moved').appendTo(place_holder_select_field);
}
});
}
.hide {
left: -9999px !important;
position: absolute !important;
visibility: hidden;
z-index: -500;
top: -9999px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<select id="cd-dropdown" name="cd-dropdown" class="cd-select">
<option value="-1" selected>Funds in your portfolio (3)</option>
<option value="1">fund1 £60,000</option>
<option value="2">fund 2 £40,000</option>
<option value="3">fund 3 £24,000</option>
<option value="4">View all funds ></option>
<option value="5">Fund 5 £40,000</option>
</select>
<input type="button" value="View all" class='view_more_button' />
<input type="button" value="View less" class='view_less_button hide' />
I have case, when my datepicker and some div with buttons overlaps in wrong way.
For this case I can set manually z-Index to datepicker(for my case z-Index:3 will be fine), but this will broke other places, as z-index is calculated by library (as I know).
So I want to set minimum z-index value to datepicker or just increase z-Index.
So I add to setting object own beforeShow function:
options.beforeShow = function (input,inst ){
inst.dpDiv.css('z-index', inst.dpDiv.css('z-index') + 2);
}
But this still didn't work.
Full Example: http://jsfiddle.net/NAgNV/2314/
P.S. I can't change z-Index of div with buttons.
ANSWER UPDATE
The event handler beforeShow runs inside the _showDatepicker method. If you wish to overwrite an internal method I suggest to use this one instead of _updateDatepicker:
$.datepicker._showDatepicker_original = $.datepicker._showDatepicker;
$.datepicker._showDatepicker = function(input) {
$.datepicker._showDatepicker_original(input);
// now change the z-index
$.datepicker.dpDiv.css('z-index', +$.datepicker.dpDiv.css('z-index') + 2);
}
Now, you don't need anymore the event beforeShow:
The snippet:
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
var $el = $(element);
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {};
$.datepicker._showDatepicker_original = $.datepicker._showDatepicker;
$.datepicker._showDatepicker = function(input) {
$.datepicker._showDatepicker_original(input);
// now change the z-index
console.log('Initial z-index: ' + $.datepicker.dpDiv.css('z-index'));
$.datepicker.dpDiv.css('z-index', +$.datepicker.dpDiv.css('z-index') + 2);
console.log('Changed z-index: ' + $.datepicker.dpDiv.css('z-index'));
}
$el.datepicker(options);
//handle the field changing
ko.utils.registerEventHandler(element, "change", function() {
var observable = valueAccessor();
observable($el.datepicker("getDate"));
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$el.datepicker("destroy");
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
$el = $(element),
current = $el.datepicker("getDate");
if (value - current !== 0) {
$el.datepicker("setDate", value);
}
}
};
var viewModel = {
myDate: ko.observable(new Date("12/01/2013")),
setToCurrentDate: function() {
this.myDate(new Date());
}
};
ko.applyBindings(viewModel);
td, th { padding: 5px; border: solid 1px black; }
th { color: #666; }
a { font-size: .75em; }
.scrolled {height : 100px;}
.btn-c{z-index: 3; position : relative;}
<link href="https://code.jquery.com/ui/1.12.0/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.js"></script>
<script src="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
<div class='scrolled'>
<input data-bind="datepicker: myDate, datepickerOptions: { minDate: new Date() }" />
</div>
<hr />
<div class='btn-c'>
<button data-bind="click: setToCurrentDate">Set To Current Date</button>
</div>
<hr />
<div data-bind="text: myDate"></div>
OLD ANSWER
Because on beforeShow z-index value is reset you may delay the:
inst.dpDiv.css('z-index', inst.dpDiv.css('z-index') + 2);
Your updated fiddle and the example:
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
var $el = $(element);
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {};
options.beforeShow = function (input,inst ){
setTimeout(function() {
inst.dpDiv.css('z-index', inst.dpDiv.css('z-index') + 2);
}, 1);
}
$el.datepicker(options);
//handle the field changing
ko.utils.registerEventHandler(element, "change", function() {
var observable = valueAccessor();
observable($el.datepicker("getDate"));
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$el.datepicker("destroy");
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
$el = $(element),
current = $el.datepicker("getDate");
if (value - current !== 0) {
$el.datepicker("setDate", value);
}
}
};
var viewModel = {
myDate: ko.observable(new Date("12/01/2013")),
setToCurrentDate: function() {
this.myDate(new Date());
}
};
ko.applyBindings(viewModel);
td, th { padding: 5px; border: solid 1px black; }
th { color: #666; }
a { font-size: .75em; }
.scrolled {height : 100px;}
.btn-c{z-index: 3; position : relative;}
<link href="https://code.jquery.com/ui/1.12.0/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.js"></script>
<script src="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
<div class='scrolled'>
<input data-bind="datepicker: myDate, datepickerOptions: { minDate: new Date() }" />
</div>
<hr />
<div class='btn-c'>
<button data-bind="click: setToCurrentDate">Set To Current Date</button>
</div>
<hr />
<div data-bind="text: myDate"></div>
Simply add !important in your css file:
.ui-datepicker {z-index:4 !important;}
http://jsfiddle.net/q693qtdt/
I've looked at some similar questions, but couldn't find a solution that worked for me. I have some counters on my page, and I want them to start counting only when I reach that div in my view. any ideas? Thanks.
Here's my code...:
// JS plugin + code for counter up
(function($) {
$.fn.countTo = function(options) {
// merge the default plugin settings with the custom options
options = $.extend({}, $.fn.countTo.defaults, options || {});
// how many times to update the value, and how much to increment the value on each update
var loops = Math.ceil(options.speed / options.refreshInterval),
increment = (options.to - options.from) / loops;
return $(this).each(function() {
var _this = this,
loopCount = 0,
value = options.from,
interval = setInterval(updateTimer, options.refreshInterval);
function updateTimer() {
value += increment;
loopCount++;
$(_this).html(value.toFixed(options.decimals));
if (typeof(options.onUpdate) == 'function') {
options.onUpdate.call(_this, value);
}
if (loopCount >= loops) {
clearInterval(interval);
value = options.to;
if (typeof(options.onComplete) == 'function') {
options.onComplete.call(_this, value);
}
}
}
});
};
$.fn.countTo.defaults = {
from: 0, // the number the element should start at
to: 100, // the number the element should end at
speed: 1000, // how long it should take to count between the target numbers
refreshInterval: 100, // how often the element should be updated
decimals: 0, // the number of decimal places to show
onUpdate: null, // callback method for every time the element is updated,
onComplete: null, // callback method for when the element finishes updating
};
})(jQuery);
jQuery(function($) {
var div_header = $('#counters').offset().top;
var window_top = $(window).scrollTop();
//alert(window_top);
//alert(div_header);
if(div_header == window_top) {
alert('success');
}
$('.hours span').countTo({
from: 50,
to: 1400,
speed: 2000,
refreshInterval: 50,
onComplete: function(value) {
console.debug(this);
}
});
$('.exposure span.numbers').countTo({
from: 50,
to: 300,
speed: 2000,
refreshInterval: 50,
onComplete: function(value) {
console.debug(this);
}
});
$('.types span').countTo({
from: 0,
to: 10,
speed: 2000,
refreshInterval: 50,
onComplete: function(value) {
console.debug(this);
}
});
});
#counters {
font-family: 'opensans', sans-serif;
color: white;
font-size: 5em;
display: block;
padding: 4%;
text-align: center;
font-weight: 100;
background-color: #222020;
}
#counters div {
width: 30%;
display: inline-block;
vertical-align: top;
}
#counters span, .exposure:after {
font-size: 0.8em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<section id="counters">
<div class="hours">
<span></span>
<h3>header1</h3>
</div>
<div class="exposure">
<span class="numbers"></span><span>%</span>
<h3>header2</h3>
</div>
<div class="types">
<span></span>
<h3>header3</h3>
</div>
</section>
I'm making a system that allows reorder of products. Each display area in a shop is a unit, which has several groups which (in turn) house several products. I am currently using knockout to load a unit via a select element, then cycling through the groups providing quantity fields for products.
This all works, but I need a select all button which sets all products in a group to have a quantity of 1. I have given the group object a function (allForOne()) which should cycle through each product in the group's observable array and set this to one, however, no matter how I bind this to the #allInOne button, it always applies this to the last group in the unit's observable group array.
For example, when the code first loads, I want to select all in the first group and make their quantity one, but it only changes the quantity in the last group.
I use the same selector in the same binding to "alert" the correct unit name (based on the unit's groupCounter property) before I call the function, but it returns two different values.
Why is it not affecting the current group? I can't work this out for the life of me and Google hasn't been much help.
Here is a JSFiddle, or you can see the code below:
Here is the view:
<div class="select-container">
<label for="unit" class="label">Unit</select>
<select name="unit" class="select-unit drop-shadow" data-bind='options: units, optionsText: "skuName", optionsCaption: "Select...", value: unit'></select>
<div>
</div>
<div class="unit-image" data-bind="with: unit">
<img data-bind="attr{ src: $parent.unit().image, alt: $parent.unit().name }">
</div>
<div data-bind="with: unit" class="clearfix products-container">
<div class="clearfix" data-bind="with: $parent.groups()[$parent.groupCounter()]">
<!--<div data-bind="style: { width: (((parseInt(limit) / parseInt($root.totalLimit)) * 100) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}" style="display: block; position: relative; float: left;">-->
<h2 data-bind="text: name" style="margin-bottom: 12px;"></h2>
<div data-bind="foreach: {data: products/*, beforeRemove: hideProduct, afterRender: showProduct*/}">
<div class="prod-page-product drop-shadow" data-bind="style: { width: ((100 / $parent.limit) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">
<p><span data-bind='text: sku'></span></p>
<div>
<div class="add-container">+</div>
<div><input type="number" class="is-numeric" min="0" data-bind='value: quantity, valueUpdate: "afterkeydown"' /></div>
<div class="remove-container">-</div>
</div>
</div>
</div>
<!--</div>-->
<div class="clearfix">
<button id="nextProdGroup" class="products-button float-left" data-bind="enable:$root.firstGroupBool, click: $root.prevGroup, style: { width: productWidth, maxWidth: ((100/4) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">Prev Group</button>
<button id="prevProdGroup" class="products-button float-left" data-bind="enable:$root.nextGroupBool, click: $root.nextGroup, style: { width: productWidth, maxWidth :((100/4) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">Next Group</button>
<!--This is the offending button binding-->
<button id="allForOne" class="products-button float-left" data-bind="click: function() { alert('Expected Group: '+$root.groups()[$root.groupCounter()].name()); $root.groups()[$root.groupCounter()].allForOne()}, style: { width: productWidth, maxWidth :((100/4) - 1)+'%', marginRight: '0.5%', marginLeft: '0.5%'}">This is what messes up</button>
</div>
</div>
</div>
<div id="shadow-overlay" class="ui-overlay" data-bind="visible: loaderBool">
<div class="ui-widget-overlay"></div>
<div id="top-overlay" class="ui-overlay" style="width: 50%; height: 80%; position: absolute; left: 25%; top: 10%;"></div>
<div id="ajax-loading-container">
<p class="ajax-loader-text">Loading...</p>
</div>
Here is the viewModel:
var Product = function(id, sku) {
var self = this;
//Properties
self.id = ko.observable(id);
self.sku = ko.observable(sku);
self.quantity = ko.observable(0);
//Methods
self.incrementQuantity = function(product) {
var previousQuantity = parseInt(self.quantity());
self.quantity(previousQuantity+1);
};
self.decrementQuantity = function(product) {
var previousQuantity = parseInt(self.quantity());
if(self.quantity() > 0)
{
self.quantity(previousQuantity-1);
}
};
};
//The object with the called function
var Group = function(name, limit, productList)
{
self = this;
//Properties
self.name = ko.observable(name);
self.nametwo = name;
self.limit = limit;
self.products = ko.observableArray();
self.productWidth = ko.pureComputed(function(a, b)
{
return ((100 / limit) - 1)+'%';
});
//Methods
//---------The offending function
self.allForOne = function() {
alert("Returned Group: "+self.name());
ko.utils.arrayForEach(self.products(), function(product) {
product.quantity(1);
});
};
//Initial population
$.each(productList, function(key, product) {
self.products.push(new Product(product.id, product.sku));
});
}
var Unit = function() {
var self = this;
//Properties
self.unit = ko.observable();
self.groups = ko.observableArray();
self.groupCounter = ko.observable(0);
self.lastGroup = ko.observable(true);
self.totalLimit = 0;
self.saved = true;
self.loaderBool = ko.observable(false);
self.firstGroupBool = ko.pureComputed(function()
{
return (self.groupCounter() < 1 ? false : true);
});
self.nextGroupBool = ko.pureComputed(function()
{
return (self.lastGroup() == true ? false : true);
});
self.unit.subscribe(function() {
self.populateGroup();
});
//Methods
self.onLoadCheck = (function() {
if(units.length == 1)
{
self.unit(units[0]);
}
});
self.populateGroup = function() {
self.loaderBool(true);
self.groups([]);
//setTimeout(function() {
self.TotalLimit = 0;
$.each(self.unit().group, function(groupKey, groupVal) {
//setTimeout(function() {
self.groups.push(new Group(groupVal.name, groupVal.limit, groupVal.product));
//}, 0);
self.totalLimit += parseInt(groupVal.limit);
});
self.groupCounter(0);
self.lastGroup(false);
self.loaderBool(false);
//}, 0);
console.log(self.groups());
console.log(self.groups()[self.groupCounter()]);
};
self.prevGroup = function(a, b) {
self.save(a, b);
var groupCounter = parseInt(self.groupCounter());
self.groupCounter(groupCounter-1);
if(self.groupCounter() != self.groups().length-1)
{
self.lastGroup(false);
}
};
self.nextGroup = function(a, b) {
self.save(a, b);
var groupCounter = parseInt(self.groupCounter());
self.groupCounter(groupCounter+1);
if(self.groupCounter() == self.groups().length-1)
{
self.lastGroup(true);
}
};
self.save = function(a, b) {
var productsToSave = self.groups()[self.groupCounter()].products();
var dataToSave = $.map(productsToSave, function(line) {
return {
id: line.id(),
quantity: line.quantity()
}
});
if(productsToSave.length == 0)
{
dialog("Error", "You cannot submit before you add products.");
return false;
}
else
{
var caller = $(b.toElement);
//sendOrder("baskets/update", {products: dataToSave}, caller);
if(caller.attr("id") == "submitProdGroup")
{
window.location = baseUrl+"basket";
}
}
};
};
Take a look at this: http://jsfiddle.net/rks5te7v/5/ I rewrote your code using a viewmodel like this (look at the url above):
function ViewModel() {
var self = this;
self.units = ko.observableArray();
self.chosenUnit = ko.observable();
self.chosenGroup = ko.observable(0);
self.goToNextGroup = function () {
self.chosenGroup(parseInt(self.chosenGroup()) + 1);
};
self.goToPrevGroup = function () {
self.chosenGroup(parseInt(self.chosenGroup()) - 1);
};
self.setGroupAs1 = function () {
var products = self.chosenUnit().groups()[self.chosenGroup()].products();
ko.utils.arrayForEach(products, function (product) {
product.quantity(1);
});
};
//loading data
var product1 = new Product('1', 'P1');
var product2 = new Product('2', 'P2');
var product3 = new Product('3', 'P3');
var product4 = new Product('4', 'P4');
var group1 = new Group('G1', [product1, product2]);
var group2 = new Group('G2', [product3, product4]);
self.units.push(new Unit('Unit 1', [group1, group2]));
};
Does it solve your problem? :)
I'm doing this plugin that takes the words and makes them pulsate on the screen:
First they appear and grow, then they vanish, change place and again appear
Working plugin:
+ function($) {
var Pulsate = function(element) {
var self = this;
self.element = element;
self.max = 70;
self.min = 0;
self.speed = 500;
self.first = true;
self.currentPlace;
self.possiblePlaces = [
{
id: 0,
top: 150,
left: 150,
},
{
id: 1,
top: 250,
left: 250,
},
{
id: 2,
top: 350,
left: 350,
},
{
id: 3,
top: 250,
left: 750,
},
{
id: 4,
top: 450,
left: 950,
}
];
};
Pulsate.prototype.defineRandomPlace = function() {
var self = this;
self.currentPlace = self.possiblePlaces[Math.floor(Math.random() * self.possiblePlaces.length)];
if(!self.possiblePlaces) self.defineRandomPlace;
self.element.css('top', self.currentPlace.top + 'px');
self.element.css('left', self.currentPlace.left + 'px');
};
Pulsate.prototype.animateToZero = function() {
var self = this;
self.element.animate({
'fontSize': 0,
'queue': true
}, self.speed, function() {
self.defineRandomPlace();
});
};
Pulsate.prototype.animateToRandomNumber = function() {
var self = this;
self.element.animate({
'fontSize': Math.floor(Math.random() * (70 - 50 + 1) + 50),
'queue': true
}, self.speed, function() {
self.first = false;
self.start();
});
};
Pulsate.prototype.start = function() {
var self = this;
if (self.first) self.defineRandomPlace();
if (!self.first) self.animateToZero();
self.animateToRandomNumber();
};
$(window).on('load', function() {
$('[data-pulsate]').each(function() {
var element = $(this).data('pulsate') || false;
if (element) {
element = new Pulsate($(this));
element.start();
}
});
});
}(jQuery);
body {
background: black;
color: white;
}
.word {
position: absolute;
text-shadow: 0 0 5px rgba(255, 255, 255, 0.9);
font-size: 0px;
}
.two {
position: absolute;
color: white;
left: 50px;
top: 50px;
}
div {
margin-left: 0px;
}
<span class="word" data-pulsate="true">Love</span>
<span class="word" data-pulsate="true">Enjoy</span>
<span class="word" data-pulsate="true">Huggs</span>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
If you notice i define the places that the word can grow in the self.possiblePlaces, and if you notice the animation, sometimes more then one word can grow in one place, my goal coming here is ask for help. How I can make two words never grow in the same place??
I was trying to do like this:
In the defineRandomPlace i pick a random object inside my possiblePlaces array:
Pulsate.prototype.defineRandomPlace = function() {
var self = this;
self.currentPlace = self.possiblePlaces[Math.floor(Math.random() * self.possiblePlaces.length)];
if(!self.possiblePlaces) self.defineRandomPlace;
delete self.possiblePlaces[self.currentPlace.id];
self.element.css('top', self.currentPlace.top + 'px');
self.element.css('left', self.currentPlace.left + 'px');
};
Notice the delete, first i clone the chosen object, after I delete it but keep his place in the array.
After the animation was over, I put the object in the array again, before starting all over again:
Pulsate.prototype.animateToZero = function() {
var self = this;
self.element.animate({
'fontSize': 0,
'queue': true
}, self.speed, function() {
self.possiblePlaces[self.currentPlace.id] = self.currentPlace;
self.defineRandomPlace();
});
But it made no difference.
Thanks!!
Pen: http://codepen.io/anon/pen/waooQB
In your example, you are randomly picking from a list that has five members, and you have three separate words that could be displayed, putting chance of overlap fairly high.
A simple approach to resolve is to pick the first item in the list, remove it from the list, and the append to the end of the list each time. Because you have more positions in the lists than items selecting from it, you're guaranteed to never collide.
Share the same list possiblePlaces between all instances.
Shift the first item off the queue, and push it onto the end when its done each time, instead of selecting randomly in defineRandomPlace.
Snippet highlighting #2:
// shift a position off the front
self.currentPlace = possiblePlaces.shift();
self.element.css('top', self.currentPlace.top + 'px');
self.element.css('left', self.currentPlace.left + 'px');
// push it back on the end
possiblePlaces.push(self.currentPlace);
If you want it truly random, you'll need to randomly select and remove an item from the array, and not put it back into the array until after it's been used. You'll also need to always ensure that you have more possiblePlaces than you have dom elements to place on the page.
Like so:
Pulsate.prototype.defineRandomPlace = function() {
var self = this;
var newPlace = possiblePlaces.splice(Math.floor(Math.random()*possiblePlaces.length), 1)[0];
if (self.currentPlace) {
possiblePlaces.push(self.currentPlace);
}
self.currentPlace = newPlace;
self.element.css('top', self.currentPlace.top + 'px');
self.element.css('left', self.currentPlace.left + 'px');
};
See http://codepen.io/anon/pen/bdBBPE
My decision is to divide the page to imaginary rows and to prohibit more than one word in the same row. Please check this out.
Note: as the code currently does not support recalculating of rows count on document resize, the full page view will not display correctly. Click "reload frame" or try JSFiddle or smth.
var pulsar = {
// Delay between words appearance
delay: 400,
// Word animation do not really depend on pulsar.delay,
// but if you set pulsar.delay small and wordAnimationDuration long
// some words will skip their turns. Try 1, 2, 3...
wordAnimationDuration: 400 * 3,
// Depending on maximum font size of words we calculate the number of rows
// to which the window can be divided
maxFontSize: 40,
start: function () {
this.computeRows();
this.fillWords();
this.animate();
},
// Calculate the height or row and store each row's properties in pulsar.rows
computeRows: function () {
var height = document.body.parentNode.clientHeight;
var rowsCount = Math.floor(height/this.maxFontSize);
this.rows = [];
for (var i = 0; i < rowsCount; i++) {
this.rows.push({
index: i,
isBusy: false
});
}
},
// Store Word instances in pulsar.words
fillWords: function () {
this.words = [];
var words = document.querySelectorAll('[data-pulsate="true"]');
for (var i = 0; i < words.length; i++) {
this.words.push(new Word(words[i], this.wordAnimationDuration, this.maxFontSize));
}
},
// When it comes time to animate another word we need to know which row to move it in
// this random row should be empty at the moment
getAnyEmptyRowIndex: function () {
var emptyRows = this.rows.filter(function(row) {
return !row.isBusy;
});
if (emptyRows.length == 0) {
return -1;
}
var index = emptyRows[Math.floor(Math.random() * emptyRows.length)].index;
this.rows[index].isBusy = true;
return index;
},
// Here we manipulate words in order of pulsar.words array
animate: function () {
var self = this;
this.interval = setInterval(function() {
var ri = self.getAnyEmptyRowIndex();
if (ri >= 0) {
self.words.push(self.words.shift());
self.words[0].animate(ri, function () {
self.rows[ri].isBusy = false;
});
}
}, this.delay);
}
}
function Word (span, duration, maxFontSize) {
this.span = span;
this.inAction = false;
this.duration = duration;
this.maxFontSize = maxFontSize;
}
/**
* #row {Numer} is a number of imaginary row to place the word into
* #callback {Function} to call on animation end
*/
Word.prototype.animate = function (row, callback) {
var self = this;
// Skip turn if the word is still busy in previous animation
if (self.inAction) {
return;
}
var start = null,
dur = self.duration,
mfs = self.maxFontSize,
top = row * mfs,
// Random left offset (in %)
left = Math.floor(Math.random() * 90),
// Vary then font size within half-max size and max size
fs = mfs - Math.floor(Math.random() * mfs / 2);
self.inAction = true;
self.span.style.top = top + 'px';
self.span.style.left = left + '%';
function step (timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
// Calculate the factor that will change from 0 to 1, then from 1 to 0 during the animation process
var factor = 1 - Math.sqrt(Math.pow(2 * Math.min(progress, dur) / dur - 1, 2));
self.span.style.fontSize = fs * factor + 'px';
if (progress < dur) {
window.requestAnimationFrame(step);
}
else {
self.inAction = false;
callback();
}
}
window.requestAnimationFrame(step);
}
pulsar.start();
body {
background: black;
color: white;
}
.word {
position: absolute;
text-shadow: 0 0 5px rgba(255, 255, 255, 0.9);
font-size: 0px;
/* To make height of the .word to equal it's font size */
line-height: 1;
}
<span class="word" data-pulsate="true">Love</span>
<span class="word" data-pulsate="true">Enjoy</span>
<span class="word" data-pulsate="true">Huggs</span>
<span class="word" data-pulsate="true">Peace</span>
I updated your plugin constructor. Notice the variable Pulsate.possiblePlaces, I have changed the variable declaration this way to be able to share variable data for all Object instance of your plugin.
var Pulsate = function(element) {
var self = this;
self.element = element;
self.max = 70;
self.min = 0;
self.speed = 500;
self.first = true;
self.currentPlace;
Pulsate.possiblePlaces = [
{
id: 0,
top: 150,
left: 150,
},
{
id: 1,
top: 250,
left: 250,
},
{
id: 2,
top: 350,
left: 350,
},
{
id: 3,
top: 250,
left: 750,
},
{
id: 4,
top: 450,
left: 950,
}
];
};
I added occupied attribute to the possible places to identify those that are already occupied. If the randomed currentPlace is already occupied, search for random place again.
Pulsate.prototype.defineRandomPlace = function() {
var self = this;
self.currentPlace = Pulsate.possiblePlaces[Math.floor(Math.random() * Pulsate.possiblePlaces.length)];
if(!Pulsate.possiblePlaces) self.defineRandomPlace;
if (!self.currentPlace.occupied) {
self.currentPlace.occupied = true;
self.element.css('top', self.currentPlace.top + 'px');
self.element.css('left', self.currentPlace.left + 'px');
} else {
self.defineRandomPlace();
}
};
Every time the element is hidden, set the occupied attribute to false.
Pulsate.prototype.animateToZero = function() {
var self = this;
self.element.animate({
'fontSize': 0,
'queue': true
}, self.speed, function() {
self.currentPlace.occupied = false;
self.defineRandomPlace();
});
};
A small hint f = 0.5px x = 100px t = 0.5s
x / f = 200
200/2 * t * 0.5 = f(shrink->expand until 100px square) per 0.5 seconds