Nativescript Repeater databinding - data-binding

I want to create a dynamic menu list, and apply class at selected item.
I have an array of menu entries, menuItems which is an observableArray.
The page binding contain two entries :
menuItems
selectedPage
The idea is simple : apply a different class when the selectedPage parameter equal the page name to indicate to user which page is currently displayed.
<Repeater items="{{menuItems}}" id="repeater">
<Repeater.itemTemplate>
<Label text="{{name}}" class="{{ $parents['Page'].selectedPage == name ? 'selected' : '' }}" tap="navigate" />
</Repeater.itemTemplate>
</Repeater>
This doesn't work, so I have made some tests, and a strange thing happened.
When I use a simple Label into my Repeater to test my bindings, I can acces the good datas.
<Label text="{{name}}"/>
Display the good menuItems entry name.
<Label text="{{$parents['Page'].selectedPage}}"/>
Display the good selectedPage entry name.
But, these code samples doesn't work together. Both works only independently.
So, I'm a bit lost, is using a $parents based selector change the context inside the Repeater ?

I too have encountered this issue and found the solution in NativeScript's documentation on data binding:
Note: Binding expression could be used without explicitly named source property ( TextField text="" ). In that case $value is used as a source property. However this could lead to problems when a nested property should be observed for changes (e.g. item.nestedProp). $value represents bindingContext and when any property of the bindingContext is changed expression will be evaluated. Since nestedProp is not a property of the bindingContext in item.nestedProp then there will be no propertyChange listener attached and changes to nestedProp will not be populated to UI. So it is a good practice to specify which property should be used as source property in order to eliminate such issues.
What this means is that when you're binding a variable that's set directly on your binding context, you can pass the expression into the curly brackets by itself:
<Label text="{{name}}" class="{{ mySelectedPage == name ? 'selected' : '' }}" tap="navigate" />
...but if you're binding a variable that's nested within an object which is set on the binding context, you must pass that nested property into the curly brackets as the first parameter, and the expression itself as the second parameter:
<Label text="{{name}}" class="{{ $parents['Page'].selectedPage, $parents['Page'].selectedPage == name ? 'selected' : '' }}" tap="navigate" />

Related

Aurelia not displaying correct representation of array after removing item

I have a list of items in an array.
When i click the item in my view i am attempting to remove this item
View
<div class="lootItem" repeat.for="lootItem of stack">
<div class="noselect" click.delegate="$parent.takeItem(lootItem)">
<i class="fa fa-diamond"></i> ${lootItem.value}
</div>
</div>
ViewModel
takeItem(lootItem){
this.eventAggregator.publish(new ItemTaken(lootItem));
console.log(_.includes(this.stack, lootItem)); //true
_.pull(this.stack, lootItem); //removes item and fails to update the ui
_.remove(this.stack, lootItem); //removes item and fails to update the ui
this.stack.shift(); //removes first item and updates the ui
}
Both .pull() and .remove() (using lodash) will remove the item in the array but not update the ui.
.shift() manages to remove an item from the array and is updating the UI.
Why is Aurelia not updating the UI despite me removing the item when using lodash?
addendum: it might be worth noting if i click the same item twice then _.includes is true the first time and then false the second time.
Aurelia can provide you with the index of the current item when inside a repeater using the $index variable. Then, simply use the built in array methods that ES2015 provides:
View
<div class="lootItem" repeat.for="lootItem of stack">
<div class="noselect" click.delegate="$parent.takeItem($index, lootItem)">
<i class="fa fa-diamond"></i> ${lootItem.value}
</div>
</div>
ViewModel
takeItem($index, lootItem){
this.eventAggregator.publish(new ItemTaken(lootItem));
this.stack.splice($index, 1);
}
I believe your problem lies in the scope of the variables.
_.pull and _.remove are returning a new instance of an array.
Try
this.stack = _.remove(this.stack, lootItem);
Using ES2015 Sets Yet another approach is to use the ES2015 Set data
structure. It can be initialized with an array of values, and the Set
prototype even provides a delete method for removing a specific value
...
However, the semantics of sets are different from regular arrays. By
definition, a set is a collection of unique values. Therefore, it
never contains duplicates. If you need a collection that allows for
duplicate values, sets are the wrong data structure.
https://blog.mariusschulz.com/2016/07/16/removing-elements-from-javascript-arrays
I am now using a Set for this and it allows me to use .add() and .delete(). I just need to remember that everything has to be unique (okay in my case here)
I'd still like to understand why manipulating the array though lodash doesn't work but i'll investigate that some other time.
Addendum
You can also use a prototype:
interface Array<T> {
remove(itemToRemove: T): Array<T>;
}
(<any>Array.prototype).remove = function (itemToRemove) {
const index = this.indexOf(itemToRemove);
if (index !== -1) {
this.splice(index, 1);
}
return this;
}

Knockout databinding of input type?

Can you databind the type of an input control? Like something like this:
I believe I can just use the and surround some controls so that I could have a different input control for each type when necessary. But I thought I'd check if there was a way to bind the type and change it on the fly that way.
The attr binding lets you bind attribute values like:
<input data-bind="attr: { type: something }" />
http://knockoutjs.com/documentation/attr-binding.html
http://jsfiddle.net/rniemeyer/h6n6oa6v/

How Do I Access a Property of bound Object Instance with Knockout

I am unable to figure out how to get to a property of a bound item. The following does not work when the .name() is there.
<span data-bind='text: selectedMenuSection().name()'></span>
Seems you can't access a property of a bound object in the data-bind. The same syntax works in a model function, MenuViewModel.showSelection(). I've tried every conceivable syntax with no success. Below is an excerpt of my code.
Html
MenuSections: <select data-bind="options: leftMenu, optionsText: 'name', value: selectedMenuSection, optionsCaption: 'Pick...'"> </select>
Selected: <span data-bind='text: selectedMenuSection().name()'></span>
<button data-bind="click: showSelection">Show Selected Info</button>
javascript
function MenuViewModel (menu) {
var self = this;
self.leftMenu = ko.observableArray(menu);
//Bound to Select
self.selectedMenuSection = ko.observable();
self.showSelection = function(){
alert(self.selectedMenuSection().name());
};
};
Is it not possible to get to a databound object property or have I just not figured out how to do it?
Can you try the following:
<span data-bind="with: selectedMenuSection">
<span data-bind="text: name"></span>
</span>
The first line makes sure that the inner span only exists if selectedMenuSection is not null or undefined (when you create self.selectedMenuSection, its value is undefined which can cause an error while trying to access the name property). The second line is your original binding. You don't need the () at the end, because you bind against the observable (you only need () to access the current value of the observable, or if you bind against a complex statement instead of directly to an observable).
What does a MenuSection look like? I'm assuming that it has a name property that is not an observable. Hans's answer is the correct way to solve your problem. Understand the javascript object and understanding what 'context' you are in when you write your knockout html is very important.
The with binding changes the context from the root element ( the object you passed into your ko.applyBindings) to the context of the selectedMenuSection. So inside of that 'with' your context ( $data in all cases ) is a MenuSection. If you are every curious about what object is in the context you can do something like this:
<div data-bind="with: selectMenuSection">
<pre data-bind="text: ko.toJSON($data)></pre>
<span data-bind="text: name"></span>
</div>
This will print out the current object context that you are in. You will then be able to see that you have a 'name' property available to you and whatever else is attached to a MenuSection.

Angular: How to bind to an entire object using ng-repeat

I'm just beginning to experiment in Angular, and confused about how best to approach binding using ng-repeat. I basically get the point about ng-repeat creating a child scope. My problem is much more basic :) For html like this:
<div ng-controller="swatchCtrl" class="swatch-panel">
Swatches
<ul>
<li ng-repeat="swatch in swatchArray" class="swatch">
<input
type="radio"
name="swatches"
ng-model="$parent.currentSwatch"
value="{{swatch}}"
>
<label class="swatch-label">
<div class="swatch-color" style="background-color: #{{swatch.hexvalue}};"></div
><span class="swatch-name">{{swatch.colorName}}</span>
</label>
</li>
</ul>
currentSwatch is:
<pre>{{currentSwatch | json}}</pre>
currentSwatchObj is:
<pre>{{currentSwatchObj | json}}</pre>
how do I tell this to fire??
swatchArray is:
<pre>{{swatchArray | json}}</pre>
</div>
and javascript like this:
function swatchCtrl($scope) {
$scope.swatchArray = [
{colorName:'Red', hexvalue: 'ff0000', selected: 'false'},
{colorName:'Green', hexvalue: '00ff00', selected: 'false'},
{colorName:'Blue', hexvalue: '0000ff', selected: 'false'}
];
$scope.currentSwatch = {};
}
http://jsfiddle.net/8VWnm/
I want to:
a) When the user clicks on a radio button, I want it to set both the colorName and the hexvalue properties of the currentSwatch object. Right now the binding seems to be giving me a stringified object from the array. How do watch the return of currentSwatch so I can parse it back to an available object? Simple, I know, but what am I missing?
b) When the user clicks on a radio button, I think I want that to set the value of the corresponding "selected" key in the original array to "true". Vice versa for unchecking. Let's say that only one swatch can ever be selected at a time in the palette. (I would like in theory to be able to iterate through the array later on, on the supposition that the different keys and values are likely to sometimes not be unique.)
This kinda stuff is super easy with jquery methods, but I'd like to learn the idiomatic angular way. Thanks in advance for any help.
http://jsfiddle.net/8VWnm/54/
Instead of listening to the ng-click event I would set the index of the selected element to a variable called "currentSwatchIndex"
<li ng-repeat="swatch in swatchArray" class="swatch">
<input
type="radio"
ng-model="$parent.currentSwatchIndex"
value="{{$index}}"
>
</li>
The you can $watch value changes of the currentSwatchIndex in your controller and set the selected swatch-Object and selection states in this $watch function:
$scope.$watch('currentSwatchIndex', function(newValue, oldValue) {
$scope.currentSwatchObj = $scope.swatchArray[newValue];
$scope.swatchArray[newValue].selected = true;
$scope.swatchArray[oldValue].selected = false;
});
Only knowing the currentSwatchIndex should be enough to identify the selected swatchObject. So probably you can get rid of the currentSwatchObj and the selected property of your swatchArray.
You can always get the selected swatch programmatically through a array access.
For future users that can come here to do the same in a select, you don't need use any index, the select must be done like this:
http://docs.angularjs.org/api/ng.directive:select

Rendering a spark view to a string

I'm trying to render a partial view as a string so it can be returned as HTML to a jquery ajax call. After a lot of searching I found this code.
public string RenderAsString(string viewName, string modelName, object model)
{
// Set up your spark engine goodness.
var settings = new SparkSettings().SetPageBaseType(typeof(SparkView));
var templates = new FileSystemViewFolder(Server.MapPath("~/Views"));
var engine = new SparkViewEngine(settings) { ViewFolder = templates };
// "Describe" the view (the template, it is a template after all), and its details.
var descriptor = new SparkViewDescriptor().AddTemplate(#"Shared\" + viewName + ".spark");
// Create a spark view engine instance
var view = (SparkView)engine.CreateInstance(descriptor);
// Add the model to the view data for the view to use.
view.ViewData[modelName] = model;
// Render the view to a text writer.
var writer = new StringWriter(); view.RenderView(writer);
// Convert to string
return writer.ToString();
}
But when the following line executes:
var view = (SparkView)engine.CreateInstance(descriptor);
I get the following error:
Dynamic view compilation failed. An
object reference is required for the
non-static field, method, or property
'DomainModel.Entities.Region.Id.get.
This is my partial view:
<ViewData Model="Region" />
<div id="${ Region.Id }" class="active-translation-region-widget" >
<label>${Region.RegionName}</label>
${ Html.CheckBox("Active") }
</div>
It doesn't seem to recognise the model.
P.S. When I call the view from a parent view like so
<for each="var region in Model">
<ActiveTranslationRegion Region="region" if="region.Active==true"></ActiveTranslationRegion>
</for>
It renders perfectly. What am I doing wrong?
Just from looking at it, I think the following line is the problem:
<ViewData Model="Region" />
Instead it should read:
<viewata model="Region" />
Note the lower case "model". This is because model is a special case since behind the scenes it performs a cast to a strongly typed viewmodel. The top one will define a variable called Model in the generated view class and assign the value Region to it. Using the lowercase option below will actually create a Model variable, but also cast it to strongly typed instance of Region that comes from the ViewData dictionary.
Note When using Model in the code though, like you did in the for each loop, it needs to be upper case which is correct in your case. Once again, this is the only special case because it's pulling a strongly typed model from the ViewData dictionary.
One other thing - <viewata model="Region" /> must be declared in the parent view, and it can only be defined once per page, so you cannot redefine it in a partial view. If it's a partial view, you should rather use it by passing in a part of the model like you have done in your second example above.
The reason for your exception above is because it's trying to get the Id property as a static item off the Region Type, rather than querying the Id property on your instance of Region as part of your viewmodel.
As a side note, the code to get where you want is a little mangled. You can find neater ways of doing what you want by checking out some of the Direct Usage Samples, but I understand this was probably just a spike to see it working... :)
Update in response to your follow up question/answer
I'm fairly sure that the problem is with passing the Region into the following call:
<ActiveTranslationRegion Region="region" if="region.Active==true">
... is again down to naming. Yes, you can only have one model per view as I said before, so what you need to do is remove the following from the top of your partial:
<viewdata model="Region" />
That's what's causing an issue. I would then rename the item going into your partial like so:
<ActiveTranslationRegion ActiveRegion="region" if="region.Active==true">
and then your partial would look like this:
<form action="/Translation/DeactivateRegion" class="ui-widget-content active-translation-region-widget">
<input type="hidden" name="Id" value="${ActiveRegion.Id}" />
<label class="region-name">${ ActiveRegion.RegionName }</label>
<input class="deactivate-region-button" type="image" src=${Url.Content("~/Content/Images/Deactivate.png")} alt="Deactivate" />
</form>
Note I'm using ActiveRegion because in the Spark parser, ActiveRegion gets declared as a variable and assigned the value of region in the current scope as you loop through the for loop. No need to stick religiously to the model - because you've gone and passed in a piece of the model now that you've declared as ActiveRegion. Oh, and you could stick with the name Region if you really want, I just changed it to make a point, and because you've got a Type called Region in your code and I'm not a big fan of the quirky issues using the same name for a variable as a type can bring about. Plus it makes it a little clearer.
The disadvantage of calling the Html.RenderPartial method is not immediately obvious. One thing you lose is the 3-pass rendering that Spark provides. If you use the tag syntax (which is preferable) you'll be able to stack partials within partials to multiple levels down passing variables that feed each partial what they need down the stack. It gets really powerful - start thinking data grid type structures where rows and cells are individual partials that are fed the variables they need from the model, all kept nice and clean in separate manageable view files. Don't stop there though, start thinking about targeting header and footer content base on variables or three column layouts that create a dashboard that renders all sorts on individually stacked partials many levels deep.
You lose all of that flexibility when you use the bog standard ASP.NET MVC Helper method Html.RenderPartial() - careful of doing that, there's more than likely a solution like the one above.
Let me know if that works...
All the best
Rob G
I refactored the code and views quite a bit. In the end all I'm really trying to acheive is have a parent view (not shown) iterate over an IEnumerable and for each iteration render a partial view (ActiveTranslationRegion) which renders some Html to represent a region model.
I also want to be a able to call an action method via an ajax call to render an indivual ActiveTranslationRegion partial view by passing it an individual region model. I've refactored the code accordingly.
Partial view (_ActiveTranslationRegion.spark)
<viewdata model="Region" />
<form action="/Translation/DeactivateRegion" class="ui-widget-content active-translation-region-widget">
<input type="hidden" name="Id" value="${Model.Id}" />
<label class="region-name">${ Model.RegionName }</label>
<input class="deactivate-region-button" type="image" src=${Url.Content("~/Content/Images/Deactivate.png")} alt="Deactivate" />
</form>
Notice by using I can refer to Model within the view as RobertTheGrey suggested (see above) .
I removed all the code to return the view as a string and simply created an action method method that returned a partialViewResult:
[UnitOfWork]
public PartialViewResult ActivateRegion(int id)
{
var region = _repos.Get(id);
if (region != null)
{
region.Active = true;
_repos.SaveOrUpdate(region);
}
return PartialView("_ActiveTranslationRegion", region);
}
One thing I had to do was amend my parent view to look like so:
<div id="inactive-translation-regions-panel">
<h3 class="ui-widget-header">Inactive Regions</h3>
<for each="var region in Model">
<span if="region.Active==false">
# Html.RenderPartial("_InActiveTranslationRegion", region);
</span>
</for>
</div>
Where previously I had the following:
<div id="inactive-translation-regions-panel">
<for each="var region in Model">
<ActiveTranslationRegion Region="region" if="region.Active==true"></ActiveTranslationRegion>
</for>
</div>
Notice I have to call the Html.RenderPartial rather than use the element. If I try and use the element (which I would prefer to do) I get the following error:
Only one viewdata model can be declared. IEnumerable<Region> != Region
Is there a way round that problem?
Update:
I tried your recommendation but with no luck. To recap the problem, I want to use the partial in 2 different situations. In the first instance I have a parent view that uses a model of IEnumerable<Region>, the partial simply uses Region as its model. So in the parent I iterate over the IEnumerable and pass Region to the partial. In the second instance I want to call PartialView("_ActiveTranslationRegion", region) from an action method. If I remove the <viewdata model="Region" /> from the partial I get an error complaining about the model. The best way round the problem I have found is to add a binding to the bindings.xml file:
<element name="partial"># Html.RenderPartial("#name", #model);</element>
(Note: It seems very important to keep this entry in the bindings file all on te same line)
This way I can still call the partial from the action method as described above and pass it a Region as the model, but I can also replace my call to Html.RenderPartial in the parent view with a more 'html' like tag:
<partial name="_ActiveTranslationRegion" model="region" />
So my parent view now looks more like this:
<div id="inactive-translation-regions-panel">
<h3 class="ui-widget-header">Inactive Regions</h3>
<for each="var region in Model">
<span if="region.Active==false">
<partial name="_ActiveTranslationRegion" model="region" />
</span>
</for>
</div>
Of course under the hood its still making a call to
# Html.RenderPartial("_ActiveTranslationRegion", region);
But its the best solution we could come up with.
Regards,
Simon

Resources