I created a currency converter object and it works great except in IE. None of options get appended to the select element. I have been trying to find a solution for hours but can't figure out what is going on. I am new to javascript so I may be doing something completely wrong just not sure what. It seems like the render method is not getting called from within fetch. Thanks
var CurrencyConverter = {
// Initialize Currency Converter
// total: jQuery wrapped object that contains the price to convert
// select: jQuery wrapped select element to render the options tag in
init: function (total, select) {
var that = this;
this.total = total;
this.base_price = accounting.unformat(this.total.text());
this.select = select;
this.fetch();
select.change(function () {
var converted = '',
formated = '';
fx.settings = { from: fx.base, to: this.value };
converted = fx.convert(that.base_price);
formated = accounting.formatMoney(converted, { symbol: this.value, format: "%s %v", precision: "0" });
$(that.total).text(formated);
});
},
// Render Currency Options
render: function () {
var that = this,
accumulator = [],
frag = '';
for (var propertyName in fx.rates) {
accumulator.push(propertyName);
}
$.each(accumulator, function ( i, val ) {
var the_price = $(document.createElement('option')).text(val);
if (val == fx.base) {
the_price.attr('selected', 'true');
}
// TODO: not optimal to run append through each iteration
that.select.append(the_price);
});
},
// Fetch & set conversion rates
fetch: function () {
var that = this;
// Load exchange rates data via the cross-domain/AJAX proxy:
$.getJSON(
'http://openexchangerates.org/latest.json',
function(data) {
fx.rates = data.rates;
fx.base = data.base;
that.render();
}
);
}
};
if ($('#currency-select')) {
CurrencyConverter.init($('#price'), $('#currency-select'));
}
Your problem is scope.
init: function (total, select) {
var that = this; // Ok, `that` is `init`...
this.total = total;
this.base_price = accounting.unformat(this.total.text());
this.select = select; // So `init.select = select`...
.
.
.
render : function () {
var that = this, // Ok, `that` is `render`
accumulator = [],
frag = '';
.
.
.
that.select.append(the_price); // ?????
The easiest way to solve this, is to create a constructor function instead of a literal object so you can pass $select as an object to which you have access within any method.
var CurrencyConverter = function($select){
this.init = function(){ ... }
this.render = function() { $select.append('...'); }
.
.
.
};
var currency = new CurrencyConverter($('select'));
Ye, i've ran too in this. Don't know if it's the right way to solve this but it works, implying that.select is a jQuery result:
that.select.get(0).add(the_price.get(0))
Tutorial about working
Related
I have used jquery dataTable in knockout.js.In that i am generating data rows from function with remove link.It will remove row form table as well form observable array.The remove link works once and remove the row from table but when i try to remove another one they do not remove it.
Here you can check http://jsfiddle.net/zongweil/PLUKv/1/
$(document).ready(function () {
/* Custom binding */
ko.bindingHandlers.dataTable = {
init: function (element, valueAccessor) {
var binding = ko.utils.unwrapObservable(valueAccessor());
// If the binding is an object with an options field,
// initialise the dataTable with those options.
if (binding.options) {
$(element).dataTable(binding.options);
}
},
update: function (element, valueAccessor) {
var binding = ko.utils.unwrapObservable(valueAccessor());
// If the binding isn't an object, turn it into one.
if (!binding.data) {
binding = {
data: valueAccessor()
};
}
// Clear table
$(element).dataTable().fnClearTable();
// Rebuild table from data source specified in binding
$(element).dataTable().fnAddData(binding.data());
}
};
/* Object code */
function GroupMember(id, name, isGroupLeader) {
var self = this;
self.id = id;
self.name = name;
self.isGroupLeader = ko.observable(isGroupLeader);
self.link = ko.computed(function () {
return "/#user/" + self.id;
});
self.nameWithLink = ko.computed(function () {
return '' + self.name + '';
});
self.actions = ko.computed(function () {
return '<a class="btn btn-danger" data-bind="click: function() {removeMember(' + self.id + ')}">' + '<i class="icon-minus-sign"></i>' + '</a>';
});
}
/* View model */
var groupViewModel = {
groupMembers: ko.observableArray([
new GroupMember("1", "Abe", true),
new GroupMember("2", "Bob", false),
new GroupMember("3", "Bill", false)])
};
groupViewModel.membersTable = ko.computed(function () {
var self = this;
var final_array = new Array();
for (var i = 0; i < self.groupMembers().length; i++) {
var row_array = new Array();
row_array[0] = self.groupMembers()[i].nameWithLink();
row_array[1] = self.groupMembers()[i].actions();
final_array.push(row_array);
}
return final_array;
}, groupViewModel);
groupViewModel.removeMember = function (id) {
var self = this;
self.groupMembers.remove(function (groupMember) {
return groupMember.id == id;
});
};
ko.applyBindings(groupViewModel);
});
When you call fnClearTable in your custom binding's update function, you clear a part of your DOM behind knockout's back.
You then add new DOM elements by calling fnAddData.
Your buttons work using the click binding. For the click binding to work, knockout has to applyBindings.
If you want to keep using both the dataTable and a click binding to work with the DOM, you'll have to manually apply bindings every time you make a change. In your init method, let knockout know you're taking care of descendant bindings:
return { controlsDescendantBindings: true };
In your update method, apply bindings by hand:
ko.applyBindingsToDescendants(viewModel, element);
This makes sure your click bindings will work again.
Here's your example with this code added: http://jsfiddle.net/5t15rhyq/
I have a javascript object with several properties and methods. I want to call the first method within the second, in order to get the default number of ingredients of a pizza and compare it with another value. However, I detect that no-value is present in the comparison of the second method.
Googling about this issue, I saw that I have to make a callback in the first method, but it didn't work for me. So, how can I be sure that the property obj.app.defaultIngredients will have a value returned by the JSON, when a 'click' event in the second method will occur? And, in that moment, I can compare the value as you also can see in the second method?
There is my (not working) code:
obj = {};
obj.app = {
defaultIngredients: '',
getDefaultIngredientsNumber: function() {
$.getJSON('/sites/all/json/pizza.json', function(data) {
var node = $('.node').attr('data-nid'),
node = 'node-' + node; // returns something like 'node-3'
$.each(data, function(key, val) {
// This returns an integer
obj.app.defaultIngredients = parseInt(data[node].general.default_ingredients);
});
}).done(function() {
return obj.app.defaultIngredients;
});
},
customAddToCart: function() {
$('#button').click(function(){
var defaultIngredients = obj.app.getDefaultIngredientsNumber();
var selectedIngredients = 0;
if defaultIngredients >= selectedIngredients) {
alert('Add some ingredients');
}
}
}
};
Some help with this will be very apreciated.
getDefaultIngredientsNumber: function(callback) {
$.getJSON('/sites/all/json/pizza.json', function(data) {
var node = $('.node').attr('data-nid'),
node = 'node-' + node; // returns something like 'node-3'
$.each(data, function(key, val) {
obj.app.defaultIngredients = parseInt(data[node].general.default_ingredients);
});
callback(obj.app.defaultIngredients)
})
},
customAddToCart: function() {
$('#button').click(function(){
obj.app.getDefaultIngredientsNumber(function(defaultIngredients) {
var selectedIngredients = 0;
if (defaultIngredients >= selectedIngredients) {
alert('Add some ingredients');
}
})
})
}
I may be missing something since seems too easy ..
});
}).done(function(data) {
return anotherfunction(data);
});
Or instead of this:
customAddToCart: function() {
$('#button').click(function(){
var defaultIngredients = obj.app.getDefaultIngredientsNumber();
var selectedIngredients = 0;
if defaultIngredients >= selectedIngredients) {
alert('Add some ingredients');
}
}
}
};
I would use this:
customAddToCart: function() {
$('#button').click(function(){
obj.app.getDefaultIngredientsNumber("alert");
}
}
};
And in the getDefaultIngredientsNumber I would check if alert is set, then perform the alert..
I am building a sidebar to filter a main view, like for instance the one at John Lewis. I have the code working but it ain't pretty.
I know there are several SO questions on similar lines but I can't quite fathom my own use case.
I need to get the names of the checkboxes from the server ( eg via JSON ) to dynamically create observableArrays on my ShopView.
Here's how it is:
var data = {
'gender' : [ ],
'color' : [ ]
};
var filterMapping = {
create: function( obj ) {
return ko.observableArray( obj.data );
}
}
var ShopView = new function() {
var self = this;
ko.mapping.fromJS( { filters: data }, filterMapping, self );
// this is the bit I don't like
this.filterChange = ko.computed(function () {
for( var key in self.filters ) {
var obj = self.filters[key];
if( ko.isObservable(obj)){
obj();
}
}
});
this.filterChange.subscribe( function( ) {
//make AJAX request for products using filter state
});
}
My HTML looks as you'd expect:
Gender
<ul>
<li><input type="checkbox" value="male" data-bind="checked: filters.gender" />Male</li>
<li><input type="checkbox" value="female" data-bind="checked: filters.gender" />Female</li>
</ul>
As I say, it works, but it's not nice. In an ideal world I could subscribe to this.filters, eg
this.filters.subscribe( function() {
//make AJAX request for products using filter state
});
NB I'm not trying to do the filtering on the client side - just update my viewmodel when the dynamically-bound checkboxes change.
Any ideas? thanks!
First, the mapping plugin should be treated as an aid to code duplication. I don't think its a good idea to think of the mapping plugin as a solution in and of itself; at least not directly. It also obscures what is happening when you post your code on SO, since we can't see the models you are working with. Just a thought.
Now, ff you want to get dynamic filters from the server, and use them to filter a list of items (like you would in a store), I would do it something like this (here is the fiddle):
var FilterOption = function(name) {
this.name = name;
this.value = ko.observable(false);
};
var Filter = function(data) {
var self = this;
self.name = data.name;
options = ko.utils.arrayMap(data.options, function(o) {
return new FilterOption(o);
});
self.options = ko.observableArray(options);
self.filteredOptions = ko.computed(function() {
var options = []
ko.utils.arrayForEach(self.options(), function(o) {
if (o.value()) options.push(o.name);
});
//If no options, false represents no filtering for this type
return options.length ? options : false;
});
};
var ViewModel = function(data) {
var self = this;
self.items = ko.observableArray(data.items);
filters = ko.utils.arrayMap(data.filters, function(i) {
return new Filter(i);
});
self.filters = ko.observableArray(filters);
self.filteredItems = ko.computed(function() {
//Get the filters that are actually active
var filters = ko.utils.arrayFilter(self.filters(), function(o) {
return o.filteredOptions();
});
//Remove items that don't pass all active filter
return ko.utils.arrayFilter(self.items(), function(item) {
var result = true;
ko.utils.arrayForEach(filters, function(filter) {
var val = item[filter.name.toLowerCase()];
result = filter.filteredOptions().indexOf(val) > -1;
});
return result;
});
});
};
The next obvious step would be to add support for items that had multiple properties, but or options properties, but this should give you the basic idea. You have a list of filters, each with any number of options (which stack additively), and you use a computed items array to store the result of filtering the items.
Edit: To get the items using an ajax subscription, you would replace the FilteredItems prop with a computed that gets the selected filters, and then subscribe to it, like this:
var ViewModel = function(data) {
var self = this;
self.items = ko.observableArray(data.items);
filters = ko.utils.arrayMap(data.filters, function(i) {
return new Filter(i);
});
self.filters = ko.observableArray(filters);
self.selectedFilters = ko.computed(function() {
ko.utils.arrayFilter(self.filters(), function(o) {
return o.filteredOptions();
});
});
self.selectedFilters.subscribe(function() {
//Ajax request that updates self.items()
});
};
Given the object:
// A data set
$.DataArea = function () {
// Default options
$.extend(this, {
class: 'DataSet',
data: new Array(),
container: null
});
// Add a bar to this object
this.addBar = function(startDate, endDate, label) {
var insertPos = this.data.length;
this.data[insertPos] = new $.DataBar();
this.data[insertPos].startDate = startDate;
this.data[insertPos].endDate = endDate;
this.data[insertPos].label = label;
this.container.children('.jobArea').append('<div class="bar-wrapper"><div class="bar">' + label + '</div></div>');
}
// Bind the bar to a div
this.bind = function(docID) {
this.container = $('#' + docID);
this.container.append('<div class="jobArea"></div>')
};
this.init = function() {
this.container.children('.jobArea .bar, .jobArea .marker').each(function(i) {
$(i).bind("selectstart", _preventDefault);
});
};
};
The line $(this).bind("selectstart", _preventDefault); I think is not working, because $(this) is conflicting with the this of the object?
How can I correctly reference the selected element in the each loop in a non conflicting way? (If that's the problem)
Edit
DataArea in use:
var MyData = new $.DataArea();
MyData.bind("container");
MyData.addBar("", "", "Bar 1");
MyData.addBar("", "", "Bar 2");
MyData.init();
Go back to using this instead of i, and use the find()[docs] method instead of the children()[docs] method.
this.init = function() {
//------------v
this.container.find('.jobArea .bar, .jobArea .marker').each(function(i) {
$(this).bind("selectstart", _preventDefault);
});
};
This is necessary becuase .bar and .marker are not direct descendants of container.
The second variable passed to the .each() callback is the actual element. You should be able to re-write it like so:
this.container.children('.jobArea .bar, .jobArea .marker').each(function(i,e) {
$(e).bind("selectstart", _preventDefault);
});
Edit
I think it's also worth mentioning that the selectstart event is not supported in all browsers which may actually be the problem.
i have this code in javascript:
var object = {
get: function(id){
sel = document.getElementById(id);
sel.orig = {};
...
return object.extend(sel, object);
}
extend: function(el, opt){
for(var name in opt) el[name] = opt[name];
return el;
}
}
and in another js i have
var Multi = {
set: function(){
if(!this.orig["height"]) this.orig["height"] = this.offsetHeight;
...
return this;
}
}
object.extend(object,Multi);
and i call it like this:
object.get('myId').set();
but when in the "set" method, the property this.orig["height"] is always undefined, so it always will change the value and that's not the idea, i need to capture it the first time because im trying to make an Fx framework and i that's for the slideUp function, i need to keep the original height, so i can go back again.
Any ideas please? thank you
Now, in contrast to some people's answers comments being completely non constructive I assume your true question regards this little ditty:
extend: function(el, opt){
for(var name in opt) el[name] = opt[name];
return el;
}
and why it returns undefined? It does'nt... your problem lies elsewhere, because this works:
var object = {
get: function(id) {
el = document.getElementById(id);
el.orig = {};
return object.extend(el,object);
},
extend: function( el, opt ) {
for(var name in opt) el[name] = opt[name];
return el;
}
}
var Multi = {
set: function() {
if(!this.orig['height']) this.orig['height'] = this.offsetHeight;
console.log( this.orig['height'] ); // if the value of offsetHeight itself is not undefined, it is what is returned.
}
}
object.extend(object,Multi);
object.get('myId').set();
hey, thanks for the comments and overall for the answer Quickredfox!! i found the problem, in this part:
get: function(id) {
el = document.getElementById(id);
el.orig = {};
return object.extend(el,object);
},
i just changed to this:
get: function(id) {
el = document.getElementById(id);
if(!el.orig) el.orig = {};
return object.extend(el,object);
},
and voila!! thank you very much for your answer!