knockout bindings not updating element values - javascript

So I am working in Knockout and I have a view model that is contains a single observable:
function rvwHistoryViewModel() {
if (document.getElementById("CCHPISentence").innerHTML != "") {
rvwHistory = ko.observable(document.getElementById("CCHPISentence").innerHTML);
}
else {
rvwHistory = ko.observable("crap");
}
}
Initially in the code above the CCHPISentence.innerHTLM == "" so the bindings are applied and my element reads "crap as it should".
However, I call this same viewModel later and at this point the CCHPISentence.innerHTLM == "some thing". I debug and see that rvwHistory is indeed set to "some thing", but the value on the screen still reads "crap".
What am I doing wrong? Thanks in advance to anybody that can help me here, I have been staring at this one a while.

This is an example of how your ViewModel can be. It contains an input and an example of a computed.
function rvwHistoryViewModel() {
this.CCHPISentence = ko.observable("");
this.rvwHistory = ko.computed(function() {
if (this.CCHPISentence() == "") {
return "crab"; // nice little animal
} else {
return this.CCHPISentence();
}
}, this);
}
ko.applyBindings(new rvwHistoryViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
CCHPISentence:
<input data-bind="value:CCHPISentence" type="text"/>
<br/>rvwHistory:<span data-bind="text:rvwHistory"></span>
tip, follow the excellent tutorial: http://learn.knockoutjs.com/

Related

Javascript "missing ) after argument list"

I'm trying to use this Javascriptsnippet below to display a DIVin a view Dynamically. But it gives me this error new:624 Uncaught SyntaxError: missing ) after argument list for this line in the snippet $("#order_country"]).change(function() {
window.onload = function() {
$("#order_country"]).change(function() {
var val = $(this).val();
$("#country_div").toggle(val == "us");
});
});
I really can't find the missing ), could anyone take a look at this and see if they can find it.
In order to make the line appear and disappear dynamically, you'll need to use javascript:
$(function() {
$("#id-you-give-to-country_select"]).change(function() {
var val = $(this).val();
$("#id-you-give-to-div").toggle(val == "us");
});
});
One gotcha is that in your if you're comparing :country (symbol) with "US" (string), which will never succeed.
Edit
Just for testing, you can also add alert(val); right after the call to toggle, so you can see that the code is running and what the values are.
EDIT - THIS VERSION WORKS
First, the country-select has an ID generated in the html, it is #order_country
Then I had to make few adjustments to the code, below is the working version
window.onload = function() {
$("#order_country").change(function() {
var val = $(this).val();
$("#country_div").toggle(val == "US");
});
};

Is there a way to write JavaScript so it is more readable when dealing with many callbacks?

I am working in JavaScript. I get more and more frustrated because of the way the code looks. The code is so nested that I very soon need to invest in a third 37" monitor to prevent damaging my fingers for all time.
Here is some working code I am working on right now:
$(function () {
var mainContainer = $('#mainContainer'),
countyContainer = $('#countyContainer'),
workAreaContainer = $('#workAreaContainer');
$('body').fadeIn(400, function() {
mainContainer.slideDown(400, function() {
ShowLoader();
ListCounties(function(counties,success) {
if (success) {
HideLoader();
for (var i = 0; i < counties.length; i++) {
if (counties[i] != "") {
countyContainer.append(
'<div class="col-md-3 county-result-item">'+
'<h3>'+counties[i]+'</h3>'+
'<i class=" '+FA('fa-folder-open','3x')+' text-center" style="color:'+RandomColor()+'"/>'+
'</div>'
);
}
}
var countyResultItem = $('.county-result-item');
countyResultItem.on('click', function(event) {
event.preventDefault();
var county = $(this).text().split("(")[0];
if (county != "") {
ShowLoader();
countyContainer.slideUp(400);
FetchWorkAreas(county,function(workAreaData,success) {
if (success) {
for (var i = 0; i < workAreaData.workAreas.length; i++) {
workAreaContainer.append(
'<div class="col-md-3 workArea-result-item">'+
'<h3>'+workAreaData.workAreas[i]+'</h3>'+
'<i class=" '+FA('fa-folder-open','3x')+' text-center" style="color:'+RandomColor()+'"/>'+
'</div>'
);
}
HideLoader();
workAreaContainer.slideDown(400, function() {
var workAreaResultItem = $('.workArea-result-item');
workAreaResultItem.on('click', function(event) {
event.preventDefault();
var selectedWorkArea = $(this).text().split("(")[0];
FetchJobListings(workAreaData.countyID,selectedWorkArea,function(jobListings,success) {
if (success) {
console.log(jobListings);
}
});
});
})
}
});
}
});
}
});
});
});
function FetchJobListings(countyID,selectedWorkArea,callback) {
$.post('Fetch.php', {fetch: 'jobListings',countyID : countyID, selectedWorkArea: selectedWorkArea}, function(data) {
if (data) {
callback(data,true);
}
});
}
function FetchWorkAreas(county,callback)
{
$.post('Fetch.php', {fetch: 'workAreasID',county:county}, function(data) {
if (data && data.workAreas.length > 0) {
callback(data,true);
}
});
}
function ListCounties(callback)
{
$.post('Fetch.php', {fetch: 'counties'}, function(data) {
if (data) {
if (data.length > 0) {
callback(data,true);
}
}
});
}
function RandomColor() {
var colors = ["#cea547","#7e8b58","#002c44","6da6a7"];
var rand = Math.floor(Math.random()*colors.length);
return colors[rand];
}
function FA(icon,size) {
var iconSize = '';
if (typeof size != undefined) {
iconSize = size;
}
return 'fa '+ icon+' fa-'+size;
}
function ShowLoader() {
if ($('.imgLoader').length === 0) {
var loaders = ["loading1.gif","loading2.gif","loading3.gif","loading4.gif"];
var rand = Math.floor(Math.random()*loaders.length);
$('#mainContainer').append('<div class="imgLoader"><img class="imgLoader img-responsive img-center" src="imgs/'+loaders[rand]+'" /><h3 class="text-center">Laster</3></div>');
}
}
function HideLoader() {
$('.imgLoader').fadeOut(400,function() {
$(this).remove();
});
}
});
This just hurt my eyes and I get a little sad really since it is important for me that code is readable and pretty, and not ending up looking like a fish ladder:
}
});
});
})
}
});
}
});
}
});
});
});
I am still what I call a novice when it comes to JavaScript and it may be something fundamentally I have not noticed. But basically, is there a way to keep the code so it doesn't indent 1000 times by the time I am done with it?
This is an example of borderline Spaghetti Code.
There are a few things you can do, the first being to split out all your functions so that they exist independently as smaller processes. For example:
ListCounties(function(counties,success) {
if (success) {
Rather than create an inline function (that isn't reusable), ask yourself what is it that I want to do with a list of counties, and build that independently.
// write a nice comment here to explain what the function does and what the arguments mean
function handleCountyList(countries, success){
}
Now you can call your ListCounties as follows:
ListCounties(handleCountyList);
By splitting out all your functions into more manageable pieces, you will avoid the ladder.
This should also make your code much more maintainable, as it becomes easier to read, think about, debug and update. Empathise with other developers, and ask, "If I showed my friend this code, could she easily understand what it does?"
If you want to get a bit more advanced, and callbacks are what is annoying you, try promises. They are semantically, and functionally better in my opinion, and allow you to do things like:
ListCounties().then(function(counties){
// do something with the counties
});
Promises come in different flavours, but this library is a good start: https://github.com/petkaantonov/bluebird
Name your callbacks and separate the logic, e.g.
ListCounties(function(counties,success) {
if (success) {....
becomes:
ListCounties(processCounties);
function processCounties(counties,success)
if (success) {...
}
Looking at your code there are a couple of things you could do, but before that it's much more important to be consistent across your codebase - with most IDEs allowing you to collapse blocks it'd be a little bit more difficult to deal with a shift in coding style than deep nesting
Guard Clauses
You could replace your
ListCounties(function(counties,success) {
if (success) {
...
with
ListCounties(function(counties,success) {
if (!success)
return;
...
to reduces 1 level of nesting. You can apply this for most of the checks that'd you do before deciding to proceed with the bulk of the code.
Be warned that this would this would make the code less readable if the guard condition is not very obvious. For instance while this is a good place for a guard clause (the guard clauses serves more like a filter)
KillWasps(function(bug) {
if (bug !== "wasp")
return
// code to deploy nuclear warheads
...
this would NOT be a good place for a guard clause (the guard block actually has program logic and 2nd block which executes for wasps is more of a fall-through block, just like a fall-through block for switch cases)
HandleInsect(function(bug) {
if (bug !== "wasp") {
// code to sign up bug for insects anonymous
return
}
// code to deploy nuclear warheads
...
An else block (and a thick magazine) is what I would use in this case.
Related reading : http://blog.codinghorror.com/flattening-arrow-code/
Delegated Event Handlers
I see you have one places where you do (for when you just added some .county-result-item elements)
var countyResultItem = $('.county-result-item');
countyResultItem.on('click', function (event) {
...
You might want to consider moving this to a delegated event handler
countyContainer.on('click', '.county-result-item', function(event) {
...
And you can move this from inside your current handler that does the append to an outer scope.
Of course, if you don't have any .county-result-item in your DOM this is a good way to perplex someone if they can't easily figure out where .county-result-item are / are added - this would be BAD, so keep your append logic close your delegated click handlers if that is the case.
Related reading : http://api.jquery.com/on/ (see the section on delegated events)
Other methods are already listed in other answers.

jstree delete_node() is not deleting

I've been cobbling together a function to put together a custom context menu for different nodes. Well, so far so good on getting different label to show up for clicks on folders or files, but not so much on actually deleting them.
Have a look. I had to ... do a little bit of a hacky workaround because I couldn't get the node.hasClass('jstree-open') yada yada to work right, but this is generally working up to the bit that's supposed to do the deleting
function customMenu(node) {
//Show a different label for renaming files and folders
var ID = $(node).attr('id');
if (ID == "j1_1") {
return items = {}; //no context menu for the root
}
var $mynode = $('#' + ID);
var renameLabel;
var deleteLabel;
var folder = false;
if ($mynode.hasClass("jstree-closed") || $mynode.hasClass("jstree-open")) { //If node is a folder
renameLabel = "Rename Folder";
deleteLabel = "Delete Folder";
folder = true;
}
else {
renameLabel = "Rename File";
deleteLabel = "Delete File";
}
var items = {
"rename" : {
"label" : renameLabel,
"action": function (obj) {
//nothing here yet.
}
},
"delete" : {
"label" : deleteLabel,
"action": function (obj) {
//tree.delete_node($(node));
//this.remove(obj);
//$('#treeView').jstree('remove', $(node));
//nothing is working.
}
}
};
return items;
}
I've put together a fiddle for your convenience: http://jsfiddle.net/dpzy8xjb/
I don't think it really needs to be said that I'm not super experienced with jQuery or dealing with third party APIs, so... Be gentle.
DO use tree.delete_node([node]); for delete.
Updated Fiddle
Edit:
The code you did is same as the node.
var ID = $(node).attr('id');
var $mynode = $('#' + ID);
Its the same object node.
I swear to god there is nothing that drives me to figure out a problem faster than posting it on StackOverflow.
Fixed:
"delete": {
"label": deleteLabel,
"action": function (obj) {
//tree.delete_node($(node));
tree.delete_node($mynode); //<<--works.
}
I had this problem, and none of the solutions worked. And as the documentation says:
all modifications to the tree are prevented (create, rename, move, delete). To enable them set core.check_callback to true.
In my case, I had a check_callback function (I'm using drag and drop) that was returning false when deleting a node.
I've adjusted it to 'delete_node' like this:
check_callback: function(operation, node, parent, position){
if(operation == 'delete_node'){
return true;
}
// ... rest of the code
}
The nodes cannot be deleted unless core.check_callback is set to true.

Access controller variables in methods dynamically

I have a controller in angular where there is a huge form split in cards. So each time a user picks a title, the card for that section shows (ex. name parts, address parts, contact data, college data, work data, etc).
The relevant code for doing that with only two sections is this:
angular.module('controllers', [])
.controller('EditUserController', function($scope, $state) {
$scope.mustShowName = false;
$scope.mustShowContact = false;
$scope.toogleShowContact = function() {
if ($scope.mustShowContact) {
$scope.mustShowContact = false;
} else {
$scope.mustShowContact = true;
}
};
$scope.toogleShowName = function() {
if ($scope.mustShowName) {
$scope.mustShowName = false;
} else {
$scope.mustShowName = true;
}
};
});
But there are various cards. There is a way of refactoring that in something like this?:
$scope.toogleSection = function(section) {
if (section) {
section = false;
} else {
section = true;
}
};
...
$scope.toogleSection($scope.mustShowName);
If I try it, it doesn't work and doesn't throw errors, so I think it is just copying the variable and not referencing the original one.
When you ask for $scope.mustShowName you just get the value and not the reference of the property - ie true or false. Instead pass the section name as a string, and refer to the property in the scope using the name.
btw - A better idea would be to create a directive that encapsulates the behavior, and will help you stays DRY.
$scope.toogleSection = function(sectionName) {
if ($scope[sectionName]) {
$scope[sectionName] = false;
} else {
$scope[sectionName] = true;
}
};
$scope.toogleSection('toogleShowName');
You can refactor it so you can send the name of the property as a parameter to the function, like this:
$scope.toggleSelection = function(sectionName) {
$scope[sectionName] = !$scope[sectionName];
};
You can use a ng-if="show" for the detailed part of the card. Then do a ng-click="show = !show". BAM! you have a toggle on touch that will show and hide whatever you put the ng-if on. Here is a example from a app I made.
<div class="item" ng-show="directions">
<!--directions go here-->
</div>
<div style="text-align: center; background-color:#284f9a;" class="item" ng-click="directions = !directions">
<p style="color: white;" ng-if="directions == false">See Directions:</p>
<p style="color: white;" ng-if="directions == true">Close Directions:</p>
</div>
with this I can show and hide the directions and change what the show/hide button says.
This also works really well with ng-repeat and only toggles the item you click on.

Mootools event causes infinite loop in IE7/IE8

I have difficulties getting this to work in IE7 (and IE8).
Its a VERY reduced part of a much more complex script. So bear in mind that the methods and the structure cannot change too much.
In IE7 I get a infinite Loop when selecting one of the Types. In FF, Chrome and IE9 it works fine. It worked with mootools 1.1 Library in IE7/IE8 great too, but since I converted it to Mootools 1.4 i got that loop problem.
Maybe some kind of event delegation change in the framework. I really don't know.
Any help is greatly appreciated!
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>eventz</title>
<script src="https://ajax.googleapis.com/ajax/libs/mootools/1.4.5/mootools-yui-compressed.js" type="text/javascript"></script>
<script type="text/javascript">
var eventz = new Class({
options: {
},
initialize: function(options) {
this.setOptions(options);
this.setup();
this.jx = 0;
},
setup: function() {
this.makeEvents();
// ...
},
makeEvents : function() {
alert("init");
var finputs = $$('.trig');
finputs.removeEvents('change');
finputs.removeEvents('click');
finputs.each(function(r) {
$(r).addEvents({
'change': function(e) {
//e.preventDefault();
alert(r.name);
new Event(e).stop();
this.refresh(r); // this needs to stay as refresh calls some ajax stuff
}.bind(this)
});
}.bind(this));
// ...
},
// refresh is called from various methods
refresh : function(el) {
if(el) {
// count types checkboxes
var ob_checked = 0;
$$('.otypes').each(function(r) {
// uncheck all if clicked on "All"
if(el.id == 'typ-0') {
r.checked = false;
}
r.checked == true ? ob_checked++ : 0 ;
})
// check "All" if non selected
if(ob_checked == 0) {
$('typ-0').checked = true;
}
// uncheck "All" if some selected
if(el.id != 'typ-0' && ob_checked != 0) {
$('typ-0').checked = false;
}
// ajax call ...
}
}
});
eventz.implement(new Options);
window.addEvent('domready', function(){
c = new eventz();
});
</script>
</head>
<body>
<fieldset class="types">
<input type="checkbox" class="trig" name="otypes[]" value="0" id="typ-0" checked="checked">All
<input id="typ-14" value="14" name="otypes[]" type="checkbox" class="otypes trig">Type A
<input id="typ-17" value="17" name="otypes[]" type="checkbox" class="otypes trig">Type B
</fieldset>
</body>
</html>
Basically in MooTools 1.4.4+, change events have been 'normalized' in IE:
see this code: https://github.com/mootools/mootools-core/blob/master/Source/Element/Element.Event.js#L170-183
and this issue: https://github.com/mootools/mootools-core/issues/2170
which traces initial commits and the fixes.
With regards to your code, some changes need to take place:
new Event(e).stop(); must be rewritten to: e.stop();
implements method is now a mutator key: Implements
The whole thing can be simplified a lot. Here's a sample refactor, optimized for performance somewhat and with clearer logic.
http://jsfiddle.net/M2dFy/5/
something like:
var eventz = new Class({
options: {
},
Implements: [Options],
initialize: function(options) {
this.setOptions(options);
this.setup();
this.jx = 0;
},
setup: function() {
this.makeEvents();
// ...
},
makeEvents: function() {
var finputs = $$('.trig');
finputs.removeEvents('change');
finputs.removeEvents('click');
var self = this;
this.type0 = $('typ-0');
this.otypes = $$('.otypes');
this.pause = false; // stop flag because of IE
finputs.each(function(r) {
r.addEvents({
click: function(e) {
this.pause || self.refresh(r); // this needs to stay as refresh calls some ajax stuff
}
});
});
// ...
},
// refresh is called from various methods
refresh: function(el) {
this.pause = true;
if (el !== this.type0) {
// count types checkboxes
this.type0.set('checked', !this.otypes.some(function(other) {
return !!other.get("checked");
}));
// ajax call ...
}
else {
this.otypes.set('checked', false);
}
this.pause = false;
}
});
now, in view of the code you had, when you change .checked, it will trigger propertychange which will try to make the event bubble.
I would recommend changing all access to checked via the .set and .get methods, eg. el.set('checked', true); / el.get('checked') - similar use for id or any other property too.
Hopefully this is enough to set you on the right path, if you were to build an example of this in jsfiddle with a minimum DOM that works, I will be happy to look it over once more.
I have no IE here (mac) but I suspect it may break on clicking on a non-all checkbox as this will fire.
I would recommend moving to click events, though this will invalidate labels:
http://jsfiddle.net/M2dFy/4/

Categories

Resources