My api call returns html, but if that html is empty e.g. I get a console html response of "", I want to display a default message using knockout. So I'm guessing that it needs to recognise that "" is empty and then display my alternate content.
View model -
var MyText = ko.observable();
var company = shell.authenticatedCompany();
hazibo.helpTextGet(company.name, company.userName, company.password).then(function (data) {
MyText(data);
});
return {
MyText: MyText
};
View -
<section class="help-text">
<div class="flex-container">
<div class="flex-item" data-bind="html: MyText">This is my alternate message if the html response is ""</div>
</div>
</section>
There are a few ways you could go about it. Personally I like to keep as much code out of the markup as possible so I would check your response data in the api callback and set it there. No need to create messy looking data bindings if you just update the observable appropriately.
hazibo.helpTextGet(company.name, company.userName, company.password).then(function (data) {
if(!data) {
MyText("This is my alternate message...");
}else{
MyText(data);
}
});
If you need to preserve what the api call actually returned you could place the logic in a computed instead, and bind to that.
One way to achieve this is to use a computed observable to determine which set of html to display:
https://jsfiddle.net/dw1284/ucnewzwo/
HTML:
<section class="help-text">
<div class="flex-container">
<div class="flex-item" data-bind="html: ItemHtml()"></div>
</div>
</section>
JavaScript:
function ViewModel() {
var self = this;
// Html populated from API call
self.MyText = ko.observable('');
// Default Html
self.Default = ko.observable('This is my alternate message if the html response is ""');
// Computed observable chooses which HTML to display (bind this to view)
self.ItemHtml = ko.computed(function() {
if (!self.MyText() || self.MyText() === '') {
return self.Default();
} else {
return self.MyText();
}
});
}
ko.applyBindings(new ViewModel());
Related
I have a View model, which has a loaddata function. It has no constructor. I want it to call the loadData method IF the ID field has a value.
That field is obtained via:
self.TemplateId = ko.observable($("#InputTemplateId").val());
Then, at the end of my ViewModel, I have a bit of code that checks that, and calls my load function:
if (!self.CreateMode()) {
self.loadData();
}
My load method makes a call to my .Net WebAPI method, which returns a slighly complex structure. The structure is a class, with a few fields, and an Array/List. The items in that list, are a few basic fields, and another List/Array. And then THAT object just has a few fields. So, it's 3 levels. An object, with a List of objects, and those objects each have another list of objects...
My WebAPI call is working. I've debugged it, and the data is coming back perfectly.
self.loadData = function () {
$.get("/api/PlateTemplate/Get", { id: self.TemplateId() }).done(function (data) {
self.Data(ko.mapping.fromJS(data));
});
}
I am trying to load the contents of this call, into an observable object called 'Data'. It was declared earlier:
self.Data = ko.observable();
TO load it, and keep everything observable, I am using the Knockout mapping plugin.
self.Data(ko.mapping.fromJS(data));
When I breakpoint on that, I am seeing what I expect in both data (the result of the API call), and self.Data()
self.Data seems to be an observable version of the data that I loaded. All data is there, and it all seems to be right.
I am able to alert the value of one of the fields in the root of the data object:
alert(self.Data().Description());
I'm also able to see a field within the first item in the list.
alert(self.Data().PlateTemplateGroups()[0].Description());
This indicates to me that Data is an observable and contains the data. I think I will later be able to post self.Data back to my API to save/update.
Now, the problems start.
On my View, I am trying to show a field which resides in the root class of my complex item. Something I alerted just above.
<input class="form-control" type="text" placeholder="Template Name" data-bind="value: Data.Description">
I get no error. Yet, the text box is empty.
If I change the code for the input box to be:
data-bind="value: Data().Description()"
Data is displayed. However, I am sitting with an error in the console:
Uncaught TypeError: Unable to process binding "value: function
(){return Data().Description() }" Message: Cannot read property
'Description' of undefined
I think it's due to the view loading, before the data is loaded from the WebAPI call, and therefore, because I am using ko.mapping - the view has no idea what Data().Description() is... and it dies.
Is there a way around this so that I can achieve what I am trying to do? Below is the full ViewModel.
function PlateTemplateViewModel() {
var self = this;
self.TemplateId = ko.observable($("#InputTemplateId").val());
self.CreateMode = ko.observable(!!self.TemplateId() == false);
self.IsComponentEditMode = ko.observable(false);
self.IsDisplayMode = ko.observable(true);
self.CurrentComponent = ko.observable();
self.Data = ko.observable();
self.EditComponent = function (data) {
self.IsComponentEditMode(true);
self.IsDisplayMode(false);
self.CurrentComponent(data);
}
self.loadData = function () {
$.get("/api/PlateTemplate/Get", { id: self.TemplateId() }).done(function (data) {
self.Data(ko.mapping.fromJS(data));
});
}
self.cancel = function () {
window.location.href = "/PlateTemplate/";
};
self.save = function () {
var data = ko.mapping.toJS(self.Data);
$.post("/api/PlateTemplate/Save", data).done(function (result) {
alert(result);
});
};
if (!self.CreateMode()) {
self.loadData();
}
}
$(document).ready(function () {
ko.applyBindings(new PlateTemplateViewModel(), $("#plateTemplate")[0]);
});
Maybe the answer is to do the load inside the ready() function, and pass in data as a parameter? Not sure what happens when I want to create a New item, but I can get to that.
Additionally, when I try save, I notice that even though I might change a field in the view (Update Description, for example), the data in the observed view model (self.Data) doesn't change.
Your input field could be this:
<div data-bind="with: Data">
<input class="form-control" type="text" placeholder="Template Name" data-bind="value: Description">
</div>
I prefer using with as its cleaner and should stop the confusion and issues you were having.
The reason that error is there is because the html is already bound before the data is loaded. So either don't apply bindings until the data is loaded:
$.get("/api/PlateTemplate/Get", { id: self.TemplateId() }).done(function (data) {
self.Data(ko.mapping.fromJS(data));
ko.applyBindings(self, document.getElementById("container"));
});
Or wrap the template with an if, therefore it won't give you this error as Data is undefined originally.
self.Data = ko.observable(); // undefined
<!-- ko if: Data -->
<div data-bind="with: Data">
<input class="form-control" type="text" placeholder="Template Name" data-bind="value: Description">
</div>
<!-- /ko -->
Also if you know what the data model is gonna be, you could default data to this.
self.Data = ko.observable(new Data());
Apply Bindings Method:
var viewModel = null;
$(document).ready(function () {
viewModel = new PlateTemplateViewModel();
viewModel.loadData();
});
I just followed this
JSFiddle example to create a little search box from an array object in my javascript. Now after some tweaking and research on search.object and filter:search:strict. Now that I have it filtering correctly, I modified a checkbox in the template that is checked upon loading the document and switched on or off based on a custom data attribute in the html that is updated by the json array.
How do I run this check once someone clears the search and the old items show back up again?
In the HTML I have this template
<div ng-repeat="o in objects | filter:search:strict | orderBy:'obj_name'" class="objectContainer">
<h3>{{o.obj_name}}</h3>
<label class="userToggleSwitch" >
<input class="userToggleInput" data-isactive="{{o.obj_isactive}}" type="checkbox">
<div class="slider round"></div>
</label>
</div>
In the JS
angular.element(document).ready(function(){
angular.module('searchApp',[])
.controller('searchCtrl', ['$scope','objHolder',function ($scope,objHolder) {
$scope.search = '';
$scope.objects = [];
$scope.objects = objHolder.getobjects();
}])
// fake service, substitute with your server call ($http)
.factory('objHolder',function(){
var objects = objList;
return {
getobjects : function(){
return objects;
}
};
});
});
The JS that modifies the items on document load is using jquery like this
$(document).ready(function(){
//console.log($('.userToggleInput'));
$.each($('.userToggleInput'), function(){
if($(this).data("isactive") == 1){$(this).attr('checked', true);}
if($(this).data("isactive") == 0){$(this).attr('checked', false);}
})
$('.userToggleInput').click(function(){
if ($(this).attr('checked') == undefined) {
// THIS IS WHERE WE WILL MAKE AN AJAX CALL TO A PHP CLASS
// TO REQUEST IF THE USER CAN BE TURNED ON - DUE TO LIC RESTRICTIONS
$(this).attr('checked',true);
} else {
$(this).attr('checked',false);
}
//console.log($(this).attr('checked'));
});
});
Created JS Fiddle to assist in Helping in this manner
I have an odd situation that I can't seem to figure out.
In Angular 1.4, I have a variable on the scope object used as a model to collect form data, $scope.videoModel. When a form is submitted, the model gets passed into a function, ng-submit="createVideo(videoModel), where a bunch of processing and regex happens. For example, extract a YouTube id.
Everything works as intended, but even though I am passing in the scope object as an argument (payload) to a function , certain attributes that are updated on the argument, also get updated on the $scope.
For eample, if I pass in $scope.videoModel.youtube into createVideo as payload, then extract the Youtube ID and assign it as payload.youtube = payload.youtube.match(regEx);, the $scope.videoModel.youtube property also gets updated. I can see this since the form value gets changed from a complete url to just the id.
It seems that passing in videoModel as an argument to a function does not create a copy to the variable, but instead references it. I can't seem to find anything about it. I believe I could simply create a new variable like this var tempVar = payload, but that seems wacky and I wonder if I'm doing something fundamentally incorrect.
Does videoModel get passed in a reference and not a copy?
Here is a sampling of the code clipped for brevity.
HTML
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">YouTube Url</label>
<div class="col-sm-10">
<input class="form-control" ng-class="{ 'has-error' : newvideo.youtube.$invalid && newvideo.youtube.$touched, 'is-valid': newvideo.youtube.$valid }" placeholder="Youtube Url" ng-model="videoModel.youtube" name="youtube" ng-pattern="youtubeValidateRegex" required>
<div class="help-block" ng-messages="newvideo.youtube.$error" ng-show="newvideo.youtube.$touched">
<p ng-message="required">YouTube Url is required.</p>
<p ng-message="pattern">Please input a valid YouTube Url.</p>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10" ng-if="!success">
<button class="btn btn-default" ng-click="newVideo(videoModel)" ng-disabled="newvideo.$invalid"> Publish</button>
</div>
</div>
JS
$scope.videoModel = {
youtube: 'https://www.youtube.com/watch?v=VkzVgiYUEIM',
vimeo: 'https://vimeo.com/193391290'
};
$scope.newVideo = function(payload) {
$scope.processing = true;
payload.user = $scope.user._id;
payload.filename = $scope.filename;
console.log(payload);
if(payload.type === 'vimeo') {
var matchVM = payload.vimeo.match($scope.vimeoExtractRegex);
console.log(matchVM);
if(matchVM) { //todo: helpers
payload.vimeo = matchVM[5];
} else {
return toastr.error('Unable to extract Vimeo ID');
}
}
if(payload.type === 'youtube') {
var matchYT = payload.youtube.match($scope.youtubeExtractRegex);
if (matchYT && matchYT[1].length === 11) { //todo: helpers
payload.youtube = matchYT[1];
} else {
return toastr.error('Unable to extract YouTube ID');
}
}
Videos.newVideo(payload)
.then(function(result) {
toastr.success('New video created.');
$scope.processing = false;
$scope.success = true;
$scope.result = result.data.result;
$scope.payload = result.data.payload; //I do assign part of the result to $scope.payload... but that still doesn't explain videoModel.youtube getting overwritten.
})
.catch(function(response) {
$scope.error = true;
toastr.error(response.data.message, response.data.status);
});
return $scope.success;
};
In JavaScript, object references are values.
Because of this, objects will behave like they are passed by reference:
If a function changes an object property, it changes the original value.
Changes to object properties are visible (reflected) outside the function.
http://www.w3schools.com/js/js_function_parameters.asp
I guess by now you might have understood what is pass by value and reference.
Here is a quick solution for you without changing much,you can use angular.copy() which will create new copy of your model into the payload variable and further you can do your rest of processing.
$scope.newVideo = function(videoModel) {
var payload = angular.copy(videoModel)
//do some processing here...
}
Hope this helps you !
Hi I have tried using an expression inside the data attribute like this
<div ng-repeat="item in items">
<canvas data="getTheData(item.value)"></canvas>
</div>
and in the controller
var getData = {
first: function(){ return angularFactory.getData() };
second: function(){ return angularFactory.getData() };
}
$scope.getTheData = function(value){
getData[value]().then(function(data){
console.log(data);
});
};
my plan is to get only the needed data from factories based on what items the user load.
the problem is this is resulting in [$rootScope:infdig] with a log that never stops even though I just have one item in the "item" list.
Am I doing this wrong?
You could have something like this, I'm not sure this will work or not
Call an getTheData on rendering of DOM, you should pass item inside that method instead of item.value
<div ng-repeat="item in items" ng-init="getTheData(item)">
<canvas data="item.data"></canvas>
</div>
Code
$scope.getTheData = function(item){
getData[item.value]().then(function(data){
item.data = data;
console.log(data);
});
};
So inside the success of getData function you need to set item.data value that will get passed to canvas data attribute.
I have following function:
$scope.setDetailToScope = function(data) {
$scope.$apply(function() {
//$scope.order = data;
$rootScope.order = data;
setTimeout(function() {
$scope.$apply(function() {
//wrapped this within $apply
$scope.order = data[0];
console.log('message:' + $scope.order);
console.log($scope.order);
});
}, 100);
});
};
"
console.log($scope.order);
Gives me values which has been set into scope.
But i cannot get these values in template.
<!-- DEBUG DIV -->
<div class="debugDiv" ng-show="$root.debugable == true">
{{columns}}
</div>
<div data-ng-controller="OrdersCtrl" ng-init="initData()">
<div id="orders_grid" >
</div>
</div>
<!-- GRID TOOLBAR BUTTON TEMPLATE -->
<script id="template" type="text/x-kendo-template">
<a class="k-button" href="\#/orders/create">Add</a>
</script>
<!-- ORDER DETAIL DIV -->
<div class="container" id="orderDetail" data-ng-controller="OrdersCtrl" ng-if="'detailSelected == true'" xmlns="http://www.w3.org/1999/html">
<!-- DEBUG DIV -->
<div class="debugDiv" ng-show="$root.debugable == true">
{{order}} <!--NOT WORKING-->
</div>
If i tried to add values into rootscope it works, but in this case i cannot get value into ng-model.
What i'm doing wrong please?
Many Thanks for any help.
EDIT:
If i tried solution wit $timeout i got on console.log($scope.order);
following object which is not passed into the template:
_events: ObjectacrCrCode: "interlos"actionName: ht.extend.initarchived: falsebaseStationInfo: ht.extend.initbsc: "bsc1"btsRolloutPlan: "plan1"candidate: "B"costCategory: ht.extend.initcreatedBy: ht.extend.initdirty: falsefacility: ht.extend.initid: 3location: ht.extend.initmilestoneSequence: undefinednetworkType: "Fix"note: "poznamka"orderNumber: 111113orderType: ht.extend.initotherInfo: ht.extend.initparent: function (){return e.apply(n||this,r.concat(h.call(arguments)))}partner: ht.extend.initpersonalInfo: ht.extend.initproject: ht.extend.initpsidCode: "psid1"sapSacIrnCode: "sap1"uid: "924c0278-88d0-4255-b8ac-b004155463fa"warehouseInfo: ht.extend.init__proto__: i
well I'm not sure why you are using a setTimeout with scope apply, to me is safer to use a $timeout since it fires another digest cycle,
try something like
$scope.setDetailToScope = function(data) {
$timeout(funtion(){
//$rootScope.order = data; try either data or data[0]
$scope.order = data[0];
},100);
};
please note that calling nested apply methods can run into some problems with the angularjs digest cycle you may get an error like "digest already in progress" so put attention to it.
NOTE:
it seems like you got some dirty data there, so try to do a map between the data and the scope
$scope.order ={};
$scope.order.uid = data.uid;
$scope.order.orderNumber = data.orderNumber //and so on
in your template try something like:
<div class="debugDiv">
<p> {{order.uid}} </p>
<p> {{ order.orderNumber}} </p>
</div>
this could be a little bit rustic but it worth to try it out.