KnockoutJS if binding with multiple elements - javascript

I thought this would be a simple thing, but I basically have an observable array of objects which contain data like:
{
BasicType: "some-type",
ExtendedType: "some-extended-type",
DisplayType: "some-display-type"
}
The data fields are always in the model, just they will be empty strings if there is no data for them to display.
Now this model is used throughout the system and is a simple POJO and the view model just contains many of these as and when they are needed in the views. So I dont want to put any view specific concerns on "this" object however can put them in the view model which is composed of lots of these smaller models.
Anyway in the view all fields are added but each has an if to disable it if there is a more applicable type available. So an example would be something like:
<div data-bind="text: BasicType, if: (BasicType && !ExtendedType && !DisplayType)"/>
<div data-bind="text: ExtendedType, if: (!BasicType && ExtendedType && !DisplayType)"/>
<div data-bind="text: DisplayType, if: (!BasicType && !ExtendedType && DisplayType)"/>
However for some reason it doesn't do as I would expect, I have tried changing the !xType for xType == '' however no such luck there either, and also used the parentheses to force evaluation but nothing. So am I missing something or can the knockoutjs if not handle multiple elements?
I would be happy to just convert these into computed observables if there was a simple way to do it, but as these models are used across pages and as a contract with a webservice (they are like the data layer) I don't want to change them, and I do not know how I could otherwise add the computed values to the objects unless when I get them I loop round each one adding the computed observables to each then using that.
Anyway that's the scenario, any advice would be great!

Your code will work if the model properties are not observables. If the model properties are observables, you need to execute the observables to get access to the values before comparing.
<div data-bind="foreach: types">
<div data-bind="text: BasicType, if: (BasicType() && !ExtendedType() && !DisplayType())"></div>
<div data-bind="text: ExtendedType, if: (!BasicType() && ExtendedType() && !DisplayType())"></div>
<div data-bind="text: DisplayType, if: (!BasicType() && !ExtendedType() && DisplayType())"></div>
</div>
Here's an example: http://jsfiddle.net/5WMVb/1/

Related

HTML Error: "Property does not exist on type" even when not displayed

I have a HTML file using AngularJS that looks like this:
<div *ngIf="Item">
<!-- Create a 'display' for each category. -->
<div *ngIf='Item.category === "book"'>
<p>Book Title: {{Item.booktitle}}</p>
</div>
<div *ngIf='Item.category === "sport"'>
<p>Sport: {{Item.sport}}</p>
</div>
</div>
The file is supposed to check what category an item is, and display properties that are relevant to that category only. These categories are for different 'item' objects, and these items do not share these properties. I have tried to make it so the application only reads properties that exist for a specific category if the item is that category, but the program appears to still read them regardless of the ngIf statement beforehand and throws a "Property does not exist on type" error even though those properties are never displayed.
My best guess on solving this is to implement an 'if' statement in the calls for specific properties to handle them being missing, like so:
<p>Book Title: {{ if Item.category ==="book" -> Item.booktitle, else -> "N/A" }}</p>
I do not know how to do so using HTML or AngularJS, or if this is even a viable solution. How would I solve this problem?
It looks like the category property on Item isn't always present.
To address this, you have a couple of options:
Use conditional chaining to handle that edge case and fail the conditional as false if the property is missing(you'll need to pre-process the JS for that to work)
Put some kind of null check in before that code to make sure the property is always there and set it to empty string or false or something

Passing data through nested components in Vue.js

I am writing a page using Vue.js and am attempting some custom components. I've started with a very simple one:
Vue.component('single-field', {
template: '<b>{{ key }}:</b> {{ value }}',
props: ['key', 'value']
});
This works fine on its own: it is intended to take two values and return them in a key: value format. Initially, when I was passing the value as an element from the Vue data object (specifically, pickup.dateTime it wasn't working. It's easier to show than explain:
<div id="app">
<single-field key="Date & Time" value="pickup.dateTime"></single-field>
</div>
I was able to fix this by binding the value (notice the colon by value):
<single-field key="Date & Time" :value="pickup.dateTime"></single-field>
I then wrote a second component which is designed to take an object and a title and return each element of the object as a set of <single-field> components, with a title above them. Here is what I wrote:
Vue.component('field-block', {
template: '<h2>{{title}}</h2>\
<div v-for="(p-key, p-value) in parent">\
<single-field key="p-key" value="p-value"></single-field>\
</div>',
props: ['parent', 'title']
});
Now, I think there must be something wrong with the way I am binding (or not binding?) which is causing the data to be displayed in a strange way. Here is my HTML:
<div id="app">
<single-field key="DateTime" :value="pickup.dateTime"></single-field>
<single-field key="Name" :value="pickup.name"></single-field>
<field-block title="Address" :parent="pickup.address"></field-block>
</div>
The field pickup.address is an object, so I was hoping that the component would iterate through the elements and return them as single fields, but what it's currently doing is just returning the variable names, like p-key: p-value. I tried doing a bind on both of them in the component definition (on both because they are now both variables being passed whereas previously I was using a static string for the key field):
<div v-for="(p-key, p-value) in parent">
<single-field :key="p-key" :value="p-value"></single-field>
</div>
But what this returns is NaN: NaN, and I can't figure out why it would do that. I don't fully understand the bind operation, but I expect that it only binds to data in the data option when defining the view, which is not where p-key and p-value are defined. How would I get the variables to 'carry through' from the outer component to the inner ones? Thanks.
Okay this one was tricky.
Several problems with your code:
Your components should have only one root element. So pack your components into a <div>.
Then, - is not a valid character to put into a javascript variable name. So you can't write (p-key, p-value) in parent, write something else, like (pKey, pValue) in parent.
On the other hand, html properties are not case-sensitive, so you should actually write key-name (for example) instead of keyName, but only for the property names. This limitation does apply only in your html files (not in your string templates).
Finally, here is a working js-fiddle.
https://jsfiddle.net/6juwLd3b/
Additionally, I would advise you that you look at your console while developing. Many error where displayed that could have lead you to a working version.

Angular 2 DOM Binding Data is not accurate

I am trying to compare two variables who are constantly change due to an API call.
Think a stock counter. If the stock increase I want to know this as well if it decreases.
Before the API call i store an instance of the call (object array) and then compare it to a new version of the call. This all works and as a result I can get the console to tell me if there was an increase or decrease.
I store this information right inside the array where I change a boolean to true or false depending on the result.
perfect, all is well and working. However when I bind these exact values to the DOM using a *ngFor loop as I want to cycle through all objects, they values are always false (default value). They never actually change even those console.log says they are.
Why would the DOM not bind the values correctly?
DOM
<div class="ticker" *ngFor="let coinresult of coinResults; let beforecoinresult of beforeCoinResults; let aftercoinresult of afterCoinResults;">
<div class="wrapper" *ngIf="coinresult.name != step2Selection">
<!--<h1 *ngIf="match === false">Before: {{beforecoinresult.amount}} - After: {{aftercoinresult.amount}}</h1>-->
<div class="pin" (click)="pinTitle(coinresult.amount, coinresult.name)">
<i class="material-icons" *ngIf="pinnedCoinAmount != coinresult.amount">gps_not_fixed</i>
<i class="material-icons selectedCoin" *ngIf="pinnedCoinAmount === coinresult.amount">gps_fixed</i>
</div>
<div class="amount" [ngClass]="{amountpinned: pinnedCoinAmount === coinresult.amount,
amountincrease: beforecoinresult.increase,
amountdecrease: beforecoinresult.decrease}">
{{coinresult.amount}}
</div>
<div class="name" [ngClass]="{ namepinned: pinnedCoinAmount === coinresult.amount,
nameincrease: beforecoinresult.increase,
namedecrease: beforecoinresult.decrease}">
{{coinresult.name}}
</div>
{{beforecoinresult.increase}} {{beforecoinresult.decrease}}
</div>
interface
export interface coinResultsType{
name: string,
amount: number,
increase: boolean,
decrease: boolean
}
Theres a Ton of logic in my component, but I don't see value in posting it, it works and works great. So I attached an image of the console who is logging:
console.log(beforeCoinResults[0].increase);
console.log(beforeCoinResults[0].decrease);
However in the CAD ticker you can see there both false. Now, they info happens quick so you may say that it had changed to false, but rest assured I am watching but they all remain false.
Instead of using console.log to debug your code, use debugger to stop the code execution and inspect the values manually. The reason is that console.log might not be in-sync with your code execution, and at the time the data is displayed on the console, it might have already changed. I know, it sounds confusing, specially thinking about console.log from a sync point of view, and not async point of view.
Your code might be working fine, and might be console.log not showing the values correctly.

Change how data is represented in AngularJS

I'm actually not sure how to ask this question, and I'm probably using incorrect terms, so bear with me.
Angular sets up a 2-way data binding so that it makes it easy to work with the data on both sides. But what if I want to change how that data is represented?
Let me give a concrete example.
I want a form with a checkbox, which if bound directly to a model, would be stored as true or false by Angular. However, in another part of the webpage I want that value to show up as 0 or 1, not true or false.
Now sure I could go and make them use two different variables, and use ng-change or something like that to update one based on the other, but that seems overkill and convoluted.
Is there some special meta function or something I can define that lets me essentially translate the data as it goes back and forth?
Use the ngTrueValue and ngFalseValue directives. They define what should be treated as true and false on a checkbox: https://docs.angularjs.org/api/ng/input/input%5Bcheckbox%5D
Example:
<input type="checkbox" ng-model="foo"
ng-true-value="OK"
ng-false-value="BOO-HOO">
The model will either have a value of "OK" or "BOO-HOO" instead of the default true and false in the above example.
Alternatively, if you want the original model to retain its default values and only draw the custom ones from another variable, you could also use the ngChange directive:
<input type="checkbox" ng-model="foo"
ng-change="bar=foo&&'OK'||'BOO-HOO'">
Now, whenever foo changes, bar will have the corresponding alternative value. Just remember to assign bar an initial value (it will start out with no value at all).
in your controller...
$scope.getvalue(data)
{
if(data===true)
return 1; // write what ever you want...
return 0;
}
in your html page..
bind the normal one as {{$scope.data1}} and other as {{getvalue($scope.data1)}}
You can do some nice things with ngBind:
Check this plunker: http://plnkr.co/edit/cRhLN2p5N4PmI65ps6Gp?p=preview
<input type="checkbox" ng-model="ok"> OK?
<h2>true or false: {{ ok }}</h2>
<h2>0 or 1: {{ ok ? 1 : 0 }}</h2>

Multi-Dimensional Arrays in Ractive.js

I've been playing around with the fantastic Ractive.js library (not to be confused with Facebook's Reactive.js). I worked out that you can render a two-dimensional using the following template code:
<div class="container">
{{ #frameContainer:i }}
<div class="row">
{{ #frameContainer[i] }}
<div on-click="cell-click" class="cell {{ . ? 'on' : 'off' }}"></div>
{{ /frameContainer[] }}
</div>
{{ /frameContainer }}
</div>
This works exactly as I'd expect and the inner cell-click event returns the correct keypath (e.g. frameContainer.2.4).
I then wanted to turn this into a three-dimensional array (to add a "time" axis). However, the following did not work:
<div class="container">
{{ #frameContainer[time]:i }}
<div class="row">
{{ #frameContainer[time][i] }}
<div on-click="cell-click" class="cell {{ . ? 'on' : 'off' }}"></div>
{{ /frameContainer[][] }}
</div>
{{ /frameContainer }}
</div>
Where time represents the current time value (it will only ever show one "time" at a time - seems obvious when you say it like that...).
This... sort of works. It displays the grid as it should, but the returned keypath for the cell-click event is no longer correct, returning something like ${frameContainer-time-8-}.2 - which has lost the time value (and has gone a bit weird).
Obviously I could just do it by having a currentFrame value which I render from and switch around with ractive.set(), but that seems less elegant. Is there a way to do it purely within the template? And, if not, what's the most efficient way of doing it otherwise?
Thanks!
This is quite a tricky one. Basically, the ${frameContainer-time-8-} keypath is how Ractive uniquely identifies an expression, and ${frameContainer-time-8-}.2 means 'the third member of whatever that expression evaluates to'.
What's happening here is this: When Ractive's parser sees the {{ #frameContainer[time][i] }} section, it parses frameContainer[time][i] as a JavaScript expression and turns it into the following (you can try this yourself - Ractive.parse('{{#frameContainer[time][i]}}')):
{
r: ['i','time','frameContainer'],
s: '${2}[${1}][${0}]'
}
When the template is rendered, Ractive creates an evaluator for that expression, which has a function generated from the string (the s property), and which watches the time and frameContainer values (it doesn't need to watch i because it can't change). When either or both of those values change, the function is executed with them as arguments. If it returns a changed value, Ractive needs to update the view.
Since there's a good mechanism for propagating viewmodel changes to the view - keypaths - that's what the evaluator uses. In order to do so, it needs to create a unique keypath, hence ${frameContainer-time-8-} (it can't contain dots or square brackets, because Ractive would try to split on those).
So here's the thing: expression keypaths are one-way. You can't do ractive.set('${frameContainer-time-8-}.2', 'true') like you can with regular keypaths, because Ractive can't figure out what underlying property that corresponds to (or if there even is one - it might be a derivative value for all it knows).
Solution 1
The simplest workaround would probably be to do something like this:
<div on-click="cell-click:{{time}},{{i}}" class="cell {{ . ? 'on' : 'off' }}"></div>
You can then use the time and i values in your cell-click handler (they will be the second and third arguments, after event).
I've done a simplified demo here (assuming I understood you correctly): http://jsfiddle.net/rich_harris/LYEGX/
Solution 2
The other way would be to do away with keypaths altogether and use an adaptor. I won't go into all the details here as it may not be the answer you're looking for, but there's some documentation and an example (some of the links are currently out of date, sorry...). In short, this method assumes you're able to use non-POJOs in your app.

Categories