edit functionality is not working in knockout js - javascript

I just created an editable using an example available on net.in this when i am trying to edit the grid then it doesn't show the value it shows the input field blank.can any body help ?
in edit i am calling this function
self.editFruit = function (fruit) {
if (self.editingItem() == null) {
// start the transaction
fruit.beginEdit(self.editTransaction);
// shows the edit fields
self.editingItem(fruit);
}
};
here is fiddle jsfiddle

At the time the binding is evaluated for the first time the editValue child observable of each of fruit's observables ( data-bind="value: name.editValue" ) doesn't exist. When you click on the "edit" link the editValue observable is created but knockout doesn't know that it has to rebind.
You can solve this 2 ways.
1 . Create a virtual if binding around each input. When the if becomes true, the content will be reinserted back into the DOM causing the bindings to re-evaluate. Make sure that editValue observable is attached to its parent BEFORE editingItem observable is set, otherwise you are in the same situation
<!-- ko if: $root.isItemEditing($data) -->
<input data-bind="..."></input>
<!-- /ko -->
2 . Make sure that all observables have the editValue observable attached to the parent observable before the model is bound, the set editValue observable's value in the beginEdit fn.
function Fruit(data) {
var self = this;
self.name = ko.observable(data.name || "");
self.name.editValue = ko.observable();
self.rate = ko.observable(data.rate || "");
self.rate.editValue = ko.observable();
}
ko.observable.fn.beginEdit = function (transaction) {
...
if (self.slice)
self.editValue(self.slice());
else
self.editValue(self());
...
}

Related

Knockout js - How to change dropdown value to previous value if certain condition is not matched

knockout js - In my HTML page, I have a dropdown and JQgrid in which user can add new rows. if user changes dropdown value before saving grid data , i want to alert user if he wants to save grid data or continue. if user wants to save data, dropdown value should be old value else new value.
I am facing issue that dropdown value is getting changed to new value.
i have knockout js subscribe method on selectedItem of dropdown. in subscribe method, logic to populate grid is present.
The subscribe method on an observable tells you what the value has been changed to after it's been changed, so unfortunately it's already too late to cancel. I think you could accomplish what you want by binding to the change event instead of using the value binding, and manually updating the observable with the new value if it passes your conditions.
var viewModel = function() {
var self = this;
self.Options = [1, 2, 3, 4];
self.selectedOption = ko.observable();
self.selectedOption.subscribe(function(newValue) {
console.log(newValue);
});
self.isOptionValid = function(value) {
return (value > 2);
}
self.onChange = function(viewModel, event) {
var target = event.target;
var newValue = $(target).val();
if (self.isOptionValid(newValue)) {
self.selectedOption(newValue);
} else {
console.log('Invalid selection');
$(target).val(self.selectedOption()); //reset to previous value
}
}
}
ko.applyBindings(new viewModel());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div>
<span>Pick a number greater than 2</span><br/>
<Select data-bind="options: Options, optionsCaption: '', event: {change: onChange}">
</Select>
</div>
This has the disadvantage of only being a one-way binding meaning that if your observable's value changes from some other source then the dropdown will not be updated with that new value. If you need to preserve a two-way binding then you might want to try a custom binding handler instead, something like: gated value binding

AngularJS, the variables doesn't change in the view

I've created this simple calculation where the user inserts his levels and by that we get the combat calculation. But the problem is, the view never change, because the variables are somehow static.
Please find jsfiddle for code.
Code link :jsfiddle
There are a few issues with your code.
First using a model for each input element does not tell angular that you wish to capture the change events, in other words that you wish angular get notified when these element value has been changed. That's why you need to use the ng-change directive, which role is to bind the new values to the DOM element.
The second issue is that angular does not know when the model values have been changed, until you do not tell to watch them.
Taking into account the above consideration this is how the refactored code should look like:
var app= angular.module("cmbtCalc", []);
app.controller('CombatCalculatorController', function($scope){
$scope.result = 0;
$scope.ALvl = 1 ;
$scope.SLvl = 1 ;
$scope.MLvl = 1 ;
$scope.RLvl = 1 ;
$scope.HLvl = 10 ;
$scope.DLvl = 1 ;
$scope.PLvl = 1 ;
function RangeMage (real){
return 0.325 * (Math.floor( real/ 2)+real);
};
$scope.RealRange=RangeMage($scope.RLvl);
$scope.RealMage=RangeMage($scope.MLvl);
['SLvl', 'ALvl', 'MLvl', 'RLvl', 'HLvl', 'DLvl', 'PLvl'].forEach(function(val) {
$scope.$watch(val, function(newValue, oldValue) {
$scope.melee = 0.325 * ($scope.ALvl + $scope.SLvl);
$scope.base = 0.25 * ( $scope.DLvl + $scope.HLvl + Math.floor ($scope.PLvl / 2 ));
})
});
$scope.calculatecmbt = function (){
$scope.result = Math.max($scope.melee, $scope.RealRange, $scope.RealMage) + $scope.base;
return $scope.result;
};
});
And inside your html you have to define a model for a ng-change directive:
<h2 ng-model="RealRang" ng-change="calculatecmbt">
{{result}}
{{calculatecmbt() | number}}
</h2>
And here is the working code: https://jsfiddle.net/tvbjscp9/5/
I think you does not invoke the function JS FIDDLE
You may have use function invoke on input changed or else use watchers for invoke functions
$scope.$watch('myVar', function(newValue, oldValue) {// You can invoke your custom functions here
});
I guess you will need to call calculatecmbt on change of value and save the output in scope variable that you bind on UI.
<p>Insert your Prayer Level <input type="number" ng-model="PLvl" ng-change="calculatecmbt()"></p>
{{outputVal}}
Controller code
$scope.outputVal = Math.max($scope.melee, $scope.RealRange, $scope.RealMage) + $scope.base;
return $scope.outputVal;
};
I made it work, wasn't passing the function values. Here's the working code :)asdasdasdas
[Working jsfiddle]( https://jsfiddle.net/tvbjscp9/8/)!

How to make sure the model is within options boundary?

I have a 2 selects handled by angularjs and the related controller.
The optins if the second select depends on the user selection in the first select.
I have this code, as follows:
<tr>
<td>
Stade<br/>
<select data-ng-options="s.displayName for s in stages"
data-ng-model="accidentSearchSelectedStage"
onChange="javascript:getAccidentsModel(this)"
>
</select>
</td>
</tr>
<tr>
<td>
Organe<br/>
<select data-ng-options="o.displayName for o in organs | filter:accidentsSearch(accidentSearchSelectedStage, null, null, accidentsDiagnosticsMenu)"
data-ng-model="accidentSearchSelectedOrgan"
onChange="javascript:getAccidentsModel(this)"
>
</select>
</td>
</tr>
EDIT 1: added filter code snippet
$scope.accidentsSearch = function( stage, organ, symptom, accidentsDiagnosticsMenu ) {
if (organ==null && symptom ==null) {
console.log("------>filter organs for criteria stage: "+stage.displayName+", "+organ+", "+symptom);
return function( organToCheck ) {
if (!accidentsDiagnosticsMenu) return false; // not yet prepared
organToCheck = organToCheck.displayName;
var pruned = accidentsDiagnosticsMenu[stage.displayName];
//console.log("Exploring pruned with "+stage.displayName+": "+JSON.stringify(pruned));
for (var o in pruned) {
// Find one with same organs?
var keep = (o == organToCheck);
if (keep) {
//console.log("Checking organs "+o+"=="+organToCheck+" for stage "+ stage.displayName+ ", so keep = "+keep);
return true;
}
}
return false;
};
}
else
if (symptom == null) {
console.log("------>filter for criteria stage/organ : "+stage.displayName+", "+organ.displayName+", "+symptom);
return function(symptomToCheck ) {
Q1) This works nice except it sometime selects an empty additional option in the second select, depending on the previous selected value it had.
How to fix this?
Q2) I need to execute legacy javascript code anytime a new selection is made. Is the 'onchange' attribute the correct way to do, or is there an angularjs way to do it?
EDIT 2: $watch path exploration
I explored a solution with a $watch on the model connected to each select as follows:
$scope.$watch('accidentSearchSelectedStage', function (newValue, oldValue) {
console.log("accidentSearchSelectedStage changed from "+oldValue.id+" to "+newValue.id);
});
$scope.$watch('accidentSearchSelectedOrgan', function (newValue, oldValue) {
console.log("accidentSearchSelectedOrgan changed from "+oldValue.id+" to "+newValue.id);
//$scope.accidentSearchSelectedSymptom = $scope.symptoms[0];
});
The logs are ok, but I'm stuck at this point for 2 reasons:
What is the time diagram between the selection of say stage, and
the execution of the filter?
Where and how to code to check the
value of organ and symptom are inbounds?
If the model is set to something other than an available option, the select directive will automatically create an option for that value. Otherwise it won't know which one should be selected. Theoretically it could be the first one, but I'm sure the Angular guys have a valid reason for doing this.
Anyway, you basically just need to bootstrap your data in your controller to make sure that if it doesn't have a valid option, just set it to the default one.
E.g.
var data = getDataFromSomeSource();
var validOptions = ['foo','bar','default'];
if(validOptions.indexOf(data.option) < 0) {
data.option = 'default';
}
RE: #2 - onchange is probably the easiest way to do that for now, no point in adding additional/unnecessary non-angular code if this is just temporary until you port everything over.

Is it possible to defer a computed observable until certain UI elements become visible?

I have a computed observable in my model which looks like this:
this.TrainingPlanTemplates = ko.computed(function ()
{
var workgroups = model.WorkgroupsImpacted();
var areas = model.AreasImpacted();
var plans = model.PrescribedPlan();
$(plans).each(function (i, v)
{
// A bunch of stuff that really slows everything down
});
// ...
}
I then have a UI template:
<table>
<!-- ko foreach: TrainingPlanTemplates -->
<tr> ... Various columns bound to TrainingPlanTemplates properties ... </tr>
<!-- /ko -->
</table>
The issue is, the HTML template above contains various custom binding handlers and potentially has a large amount of data. Rendering this table is somewhat slow (like 5 seconds or so). This UI uses the jQuery UI tabs control, so I don't even show the data when the page loads. Most users will never even switch to that tab, meaning I'm usually wasting my time binding that data.
Question: Is there a way to defer the binding of a computed observable until I say so, for example, until a certain jQuery tab becomes active?
Ideas:
I got a few ideas from this page. There does exist a deferEvaluation property, however this will only defer the property until something accesses it, which will happen immediately as a hidden HTML table is still bound to the data.
One idea would be to create a new observable property called TrainingPlanTemplatesLoaded, and set that to true when the tab becomes active. Then, create a dependency between TrainingPlanTemplates and TrainingPlanTemplatesLoaded so that when TrainingPlanTemplatesLoaded changes, TrainingPlanTemplates actually loads in the real data.
Any other ideas on the best way to accomplish this?
Yes, just making another observable that you check before doing your computation:
// set to true when you want the computation to run
this.TrainingPlanTemplatesLoaded = ko.observable(false);
this.TrainingPlanTemplates = ko.computed(function ()
{
if (this.TrainingPlanTemplatesLoaded()) {
var workgroups = model.WorkgroupsImpacted();
var areas = model.AreasImpacted();
var plans = model.PrescribedPlan();
$(plans).each(function (i, v)
{
// A bunch of stuff that really slows everything down
});
// ...
}
}, this);
Sure its possible, see my example:
function VM(){
var self = this;
self.FirstName = ko.observable("John");
self.LastName = ko.observable("Smith");
self.canShow = ko.observable(false);
self.FullName = ko.computed(function(){
if (self.canShow()){
return self.FirstName() + " " + self.LastName();
}
});
}
myVm = new VM();
ko.applyBindings(myVm);
// Represents that at some point
// Some function make something happen
setTimeout(function(){
// Let's say we check if an element was visible
// or check anything that we want to know has happened, then:
myVm.canShow(true);
}, 4000);
<p data-bind="text: FirstName"></p>
<p data-bind="text: LastName"></p>
<p data-bind="text: FullName"></p>

How do I correctly reference an attribute of the current element passed into knockout.JS?

Working on a little feedback form and I'm new at the Knockout/jQuery game so I'm sure this is a syntax error.
Goal / Background
I have a feedback form, part of which includes a list with feedback types. The actual text of the feedback type I'd like to use is stored in the "Title" attribute of the LI tags.
I'd like to pass an onclick from each of a set of LI tags denoting the type of feedback.
I would like knockout to receive this onclick event with the calling element
I'd like the ViewModel function to update the ViewModel's feedback type based on the content of the LI's title attribute
I'd then like to remove a class from all the list and apply it to the selected element.
I already have jQuery that does this; just want to incorporate it into the model change.
What I Have So Far
The relevant part of the HTML Feedback Form (the UL list):
<ul class="thumbnails" id="feedbackList">
<li class="feedbackItem" id="feedbackItemPraise" title="Praise" data-bind="click: updateFeedbackType"><i class="icon-thumbs-up"></i>Praise</li>
<li class="feedbackItem" id="feedbackItemCriticism" title="Criticism" data-bind="click: updateFeedbackType"><i class="icon-thumbs-down"></i>Criticism</li>
<li class="feedbackItem" id="feedbackItemProblem" title="Problem" data-bind="click: updateFeedbackType"><i class="icon-warning-sign"></i>Problem</li>
<li class="feedbackItem" id="feedbackItemQuestion" title="Question" data-bind="click: updateFeedbackType"><i class="icon-question-sign"></i>Question</li>
</ul>
The ViewModel so far (including some irrelevant parts):
var FeedbackViewModel = function () {
var self = this;
self.manualEMailAddress = "MyEmail#MyProvider.com";
self.manualApplicationName = "MyApplication";
self.username = ko.observable($("#feedbackUsernameFromServer").val());
self.feedbackType = ko.observable("Praise");
self.wantsFollowUp = ko.observable(true);
self.enteredName = ko.observable("");
self.feedbackText = ko.observable("");
self.userNameCaptured = ko.computed(function () { return self.username().length > 3; }, self);
self.mailToLink = ko.computed(function () { return "mailto:" + self.manualEMailAddress + "?subject=" + encodeURIComponent(self.feedbackType()) + encodeURIComponent(" for ") + encodeURIComponent(self.manualApplicationName) + "&body=" + encodeURIComponent(self.feedbackText()) }, self);
};
var feedbackViewModel = new FeedbackViewModel();
ko.applyBindings(feedbackViewModel, document.getElementById("feedbackModal"));
The current jQuery to change the style (not linked to the model yet):
$("#feedbackList li").click(function () {
$("#feedbackList li.feedbackItem-Highlighted").removeClass("feedbackItem-Highlighted");
$(this).addClass("feedbackItem-Highlighted");
});
What I think I need to add to the ViewModel, but doesn't quite work:
self.updateFeedbackType = function (elementToChangeTo) {
self.feedbackType($(elementToChangeTo).attr("title"));
$("#feedbackList li.feedbackItem-Highlighted").removeClass("feedbackItem-Highlighted");
$(elementToChangeTo).addClass("feedbackItem-Highlighted");
};
This results in feedbackType being turned into an undefined and the visual change not happening.
Where am I going wrong? Thanks for any help!
I think you just needed that function in the definition of the vm.
Here's a jsfiddle that seems to work:
http://jsfiddle.net/gN3HV/
Update: Here's a fiddle which better leverages knockout and properly accomplishes the goal:
http://jsfiddle.net/gN3HV/7/
elementToChangeTo returns the FeedbackViewModel (same as this) and not the element clicked on--the behavior is a bit different than jQuery.
The second argument passed into updateFeedbackType will be an event, so you could use $(event.target) to get a reference to the clicked element.
self.updateFeedbackType = function (view, event) {
var $elementToChangeTo = $(event.target);
self.feedbackType($elementToChangeTo.attr("title"));
$("#feedbackList li.feedbackItem-Highlighted").removeClass("feedbackItem-Highlighted");
$elementToChangeTo.addClass("feedbackItem-Highlighted");
};
However, #daedalus28 has addressed the larger problem, which is that you're not utilizing knockout.js's strengths and are over-complicating the process. You don't really need both to solve this simplistic condition.

Categories

Resources