I am writing a sample program in polymer. I have a template as shown below:
<template is="dom-repeat">
<li class="clearfix title-container">
<input type="checkbox" checked="{{item.checked::change}}" class="checkbox-container float--left" />
<label class="label-container">{{item.id}}</label>
</li>
</template>
I have a button as shown below on click of which function f1() is called:
<button on-click="f1">Save</button>
Initially all the checkboxes are checked. The user might uncheck some of the checkboxes and then click on the button. f1() collects all the elements that have been checked by the user. After execution of f1, I want all the checkboxes to be checked again. The code for f1() is shown below:
//Collect all the selected items in a separate array.
//Revert unchecked checkboxes to checked.
f1: function() {
newIndex = 0;
for (index = 0; index < this.dataArray.features.length; index++) {
if (this.dataArray.features[index].checked) {
this.newDataArray.selectedElements[newIndex++] = this.dataArray.features[index];
} else {
this.dataArray.features[index].checked = true;
}
}
}
The following is the definition of dataArray element (I just printed it in a console):
//The dataArray when printed in a console
{
type: "FeatureCollection",
features: Array(4)
}
features: Array(4) 0: {
type: "Feature",
checked: true
}
1: {
type: "Feature",
checked: true
}
2: {
type: "Feature",
checked: true
}
3: {
type: "Feature",
checked: true
}
The problem I am facing is that after f1() executes the changes made to the checked property are not reflected in the UI even though I have assigned all the values in the array to be checked. I have read about sub-properties not being reflected directly but I don't understand how do I use a notifyPath() here or how do I use the array mutation methods. Any help is appreciated.
You have to use array mutation methods.. Polymer isn't noticed about changes in arrays, so you have to call notifyPath or just use array mutation methods .
Related
I have bound an isSelected(item) function to my button which will toggle the class to show if it is selected or not, however this function will not update on changes.
I can not use a computed property as it takes a parameter.
Is there any way to make this work with a bound function and getting it to update ideally with a #computedFrom decorator or the like?
Example here:
https://codesandbox.io/s/aurelia-typescript-sandbox-8oksr?fontsize=14
You will notice that person 2 is bound correctly via the function, but clicking the other items will not update the UI.
-
reason for doing this
The reason I want to do this is a result of a somewhat complex source array. Rather than the persons array I have given as an example here. My real domain is closer to a list of boxes with items that can have other boxes (potentially infinitely) where items can be selected at any level.
Update 2
So I ran across this issue on github. One of the answers indicated that passing an observed item into a method automatically makes the method observable. Currently, you're only passing person into isSelected(). But, person isn't being changed. I think you can accomplish what you're looking for by changing your isSelected() method like so (notice the change in the call to isSelected in the class binding of the button):
vm.ts
public isSelected(person, length){
return this.selections.find(item => item.name === person.name);
}
view.html
<button
class="btn-gradient mini ${isSelected(person, selections.length) ? 'green': 'red'}"
click.delegate="selectPerson(person)"
repeat.for="person of people">
${person.name}
</button>
Example: https://codesandbox.io/s/aurelia-typescript-sandbox-oelt7?fontsize=14
Original Post
I'm struggling with the same issue with trying to implement an isSelected() method for controlling a selected indicator class. I've looked into #computedFrom.
I may be wrong on this, but from what I've seen #computedFrom can only be used with un-paramaterized getters.
#computedFrom('firstName', 'lastName')
get fullName() { return `${firstName} ${lastName}`}
So the problem with what we're wanting to do is that we need to pass in an index or an item to our method -- which breaks our ability to use #computedFrom.
An alternative, which I don't really like ... but it does work, is to add an isSelected property to each of your person objects. Then your code would look something like this:
vm.ts
selectPerson(person){
person.isSelected = !person.isSelected; //De-selects if already selected
}
view.html
<button
class="btn-gradient mini ${person.isSelected ? 'green': 'red'}"
click.delegate="selectPerson(person)"
repeat.for="person of people">${person.name}</button>
(or, as was recently suggested to me, wrap your person object in a wrapper class)
public class SelectableWrapper {
constructor(public person : Person, public isSelected : boolean){}
}
Update 1
To address the issue of displaying the list of selected items (as well as "coloring" the selected items), you could do the following (in addition to what I've already shown):
vm.ts
//Remove selections property and add it as a getter
get selections(){
return this.people.filter(p => p.isSelected);
}
view.html
<div repeat.for = "person of selections">
${person.name}
</div>
Example here: https://codesandbox.io/s/aurelia-typescript-sandbox-u92nk?fontsize=14
multi select utilizing signaler
import "./app.css";
import {BindingSignaler} from 'aurelia-templating-resources';
import { inject } from "aurelia-framework";
#inject(BindingSignaler)
export class App {
message = "Hello World!";
message2 = "Hello World2!";
people = [{ name: "Person 1" }, { name: "Person 2" }, { name: "Person 3" }];
selections = [];
constructor(private signaler: BindingSignaler) {
this.selections.push(this.people[1]);
}
selectPerson(person) {
this.selections.push(person);
this.signaler.signal('select-signal')
}
color(person) {
return this.selections.includes(person) ? 'green' : 'red';
}
}
<template>
<h1>People</h1>
<div repeat.for="person of selections">
${person.name}
</div>
<button
class="btn-gradient mini ${color(person) & signal:'select-signal' }"
click.delegate="selectPerson(person)"
repeat.for="person of people"
>
${person.name}
</button>
</template>
single select
add selected into class,
assign person in it on change
and then use selected === person as condition
import "./app.css";
export class App {
message = "Hello World!";
message2 = "Hello World2!";
people = [{ name: "Person 1" }, { name: "Person 2" }, { name: "Person 3" }];
selections = [];
// add selected
selected = null;
constructor() {
// use selectPerson
this.selectPerson(this.people[1]);
}
selectPerson(person) {
this.selections.push(person);
// assign person to selected
this.selected = person;
}
}
<template>
<h1>People</h1>
<div repeat.for="person of selections">
${person.name}
</div>
<!-- use `selected === person` as condition -->
<button
class="btn-gradient mini ${selected === person ? 'green': 'red'}"
click.delegate="selectPerson(person)"
repeat.for="person of people"
>
${person.name}
</button>
</template>
This is the code snippet but I'm not able to understand how the observer method is working
static get properties() {
return {
selected: {
type: Object,
observer: '_selectedChanged'
}
};
}
_selectedChanged(selected, oldSelected) {
if (oldSelected) oldSelected.removeAttribute('selected');
if (selected) selected.setAttribute('selected', '');
}
connectedCallback() {
super.connectedCallback();
this.selected = this.firstElementChild;
}
full code: https://codelabs.developers.google.com/codelabs/polymer-2-carousel/index.html?index=..%2F..%2Findex#3
What is selected and oldselected and how can we do oldSelected.removerAttribute?
Are these objects of elements?
Please elaborate!
selected is property of element. It's value is some HTML element (in this case it's always img i think) so, in selected property there is always saved reference to img somewhere in html. When this property change, function _selectedChanged is called with 2 arguments. first argument is new image that is currently saved in selected and second argument is old image(previous value of selected).
further in tutorial you can see code
const elem = this.selected.nextElementSibling;
if (elem) {
this.selected = elem;
}
where is shown that const elem takes some html element and put it into this.selected.
So inside function _selectedChanged they removed html attribute from old image that was previously selected (so it was visible on screen) and added new html attribute to new image that should be visible on screen for now.
You can imagine that img with attribute selected is the only one that is shown on the screen at the time
I hope you understand my explanation. My english isn't 100% so if you have question, ask me and i can try to explain it more.
EDIT
Some example with binding and observer:
Let's say we have some paper-input which should show some results (articles for example) based on value of this input. So we have some HTML:
<paper-input value="{{search}}" label="Find articles"></paper-input>
this is primitive. Just some paper-input with value stored in search property. inside script tag:
Polymer({
is: 'test-el',
properties: {
search: {
type: String,
observer: "_findResults"
},
articles: {
type: Array
}
},
_findResults() {
this.set("articles", ['firstArticle', 'secondArticle', Math.random()]);
},
});
Explain: we defined property search and articles. Whenever property search changes, it calls function _findResults (because of observer). Function _findResults do only one thing. this.set("articles") is almost same as this.articles =. More about this can be found in documentation. So this.set("articles", ['firstArticle', 'secondArticle', Math.random()]); means it creates array and set it to articles property. and now, when we have some array that is changing everytime user enter some value in paper-input, we can add some HTML to show these articles:
<template is="dom-repeat" items="{{articles}}" as="item">
[[item]] <br>
</template>
I made also fiddle, so you can play with it and understand it a little bit more.
https://jsfiddle.net/2va41sy0/1/
Your question at the beginning was almost same in difference that they stored in some property reference to HTML object and not only just string. This is also about understand some basics of javascript and not polymer
When initializing a property to an object or array value, use a function to ensure that each element gets its own copy of the value, rather than having an object or array shared across all instances of the element.
this is from the official polymer document my question here is why to not to share this default value across multiple instance as this default value will only be called once during initialization ??
<dom-module id="title-element">
<template>
<h1 style$="background-color: {{backgroundColor}}">{{config.title}}</h1>
</template>
<script>
Polymer({
is: 'title-element',
properties: {
config: {
type: Object,
notify: true,
value: {
title: 'This is my element',
}
},
backgroundColor: String,
},
ready: function () {
this.addEventListener('config-changed', function () {
console.log('config had changed');
this.querySelector('h1').style.backgroundColor = 'blue';
})
}
})
</script>
</dom-module>
<title-element background-color="yellow"></title-element>
<title-element background-color="green"></title-element>
in the above example i tried to change the value of config.title by selecting that element in chrome console and change it once using $0.config = {"title":"any other"} and also using notifyPath and as expected it changed only in the selected element not all instances
what is the purpose of using function wrap then ?
So that every element gets it own copy instead of sharing it.
If you provide a function, Polymer calls the function once per element instance.
When initializing a property to an object or array value, use a
function to ensure that each element gets its own copy of the value,
rather than having an object or array shared across all instances of
the element.
Here's the link to documentation
Here's a simple test case to depict the same.
<link rel="import" href="http://polygit.org/components/polymer/polymer.html">
<dom-module id="shared-object">
<template>
<style></style>
<div on-tap='changeValue'>Shared Object: {{shared.key}}</div>
<div on-tap='changeValue'>Not Shared Object: {{notShared.key}}</div>
</template>
</dom-module>
<script>
Polymer({
is: 'shared-object',
properties: {
shared: {
type: Object,
value: {
key: 'value'
}
},
notShared: {
type: Object,
value: function() {
return {
key: 'value'
}
}
}
},
changeValue: function() {
this.set('shared.key', 'value1');
this.set('notShared.key', 'value1');
}
})
</script>
Instance one
<br>
<shared-object id='obj1'></shared-object>
<br>
<br>Instance two
<br>
<shared-object id='obj2'></shared-object>
<script>
document.querySelector('shared-object').addEventListener('tap', function() {
console.log('First instance:\nshared value:', document.querySelector('#obj1').shared, '\nnot shared value:', document.querySelector('#obj1').notShared);
console.log('second instance:\nshared value:', document.querySelector('#obj2').shared, '\nnot shared value:', document.querySelector('#obj2').notShared);
})
</script>
After you tap on any of the value you'll notice that even though the display values are correct for all the cases but in console shared object has same value for both the instances, whereas notSharedObject has different value even in console.
Goal
When I login and logout of my app, I want to automatically load data from my <firebase-document> element as follows.
my-settings.html
<firebase-document location="[[app.firebase.settings]]"
data="{{app.settings}}">
</firebase-document>
Expected Behavior
From a logged in state, I expect to be able to logout. Then log back in and have app.settings automatically populate. In other words:
console.log
app == { foo: bar,
settings: baz
}
Actual Behavior
From a logged in state, if I logout then log back in, the app object does not contain a settings property at all. In other words:
console.log
app == { foo: bar }
Reload Browser as Workaround Hack
As a hack, I'm able to get the expected result if I first reload the browser. logout > reload browser > login yields the following desired result:
console.log
app == { foo: bar,
settings: baz
}
Question
What approach should I try to get the settings property to automatically populate my app object after logout > login without having to reload as an intermediate step? i.e., logout > reload browser > login.
Is this maybe a lifecycle or registration issue I'm not understanding? Or something else? (I just need a general direction to pursue.)
Per this answer, the code that works is as follows:
my-settings.html
<firebase-document location="[[app.firebase.settings]]"
data="{{settings}}">
</firebase-document>
<paper-input value="{{settings.email}}"
label="Email">
</paper-input>
...
<script>
(function(){
Polymer({
is: 'my-settings',
properties: {
app: {
type: Object,
notify: true
},
settings: {
type: Object,
notify: true,
observer: '_settingsChanged'
},
_settingsChanged: function() {
this.set( 'app.settings', this.settings);
}
});
})();
</script>
my-controller.html
<my-settings id="settings"></my-settings>
...
_handleLogout: function() {
this.$.settings.set('settings', {});
},
Try settings instead of app.settings for fixing the value update issue.
For hiding the values you can use a dom-if template and set security read/write rules on Firebase.
<template is="dom-if" if="[[user]]">
<firebase-document
location="[[app.firebase.settings]]"
data="{{settings}}">
</firebase-document>
</template>
<firebase-auth location="[[app.firebase]]" user="{{user}}"></firebase-auth>
How to disable an option of a kendoiu drop down list?
I couldn't find how to accomplish this in their documentation...
Try the following approach (here and here there are some demos): use a template for your items, which conditionally adds a class to the items to be disabled. The info about which items should be disabled comes from the underlying data objects.
HTML:
<script id="template" type="text/x-kendo-tmpl">
#
if (data.disabled != null) {#
<span class="tbd" > ${data.text} - is disabled </span>
# } else { #
<span>${data.text}</span > #
}#
</script>
<input id="color" value="1" />
jQuery and Kendo UI (note here the disabled extra property for the Orange item and the usage of the dataBound event):
var data = [{
text: "Black",
value: "1"
}, {
text: "Orange",
value: "2",
disabled: "disabled"
}, {
text: "Grey",
value: "3"
}];
$("#color").kendoDropDownList({
dataTextField: "text",
dataValueField: "value",
dataSource: data,
index: 0,
template: kendo.template($("#template").html()),
dataBound: function (e) {
$(".tbd").parent().click(false);
}
});
CSS for graying out:
.tbd{
color:#777777
}
While the accepted answer will prevent a click on the item, it still allows keyboard navigation (and feels pretty hackish).
Using the DataItems to identify which item should be disabled is indeed the way to go, but instead of removing the click handler, it is simpler to implement a Select handler that will stops the chain. This method is supported and documented by Kendo :
Fired when an item from the popup is selected by the user either with
mouse/tap or with keyboard navigation.
...
e.preventDefault Function
If invoked prevents the select action. The widget will retain the
previous selected item.
All that remains is to detect if we want to cancel the selection or not, which is trivial if your data item keeps a property that identifies whether it is available or not :
function Select(e) {
if (e.sender.dataItem(e.item).disabled) {
e.preventDefault();
}
}
Using a template to inject a specific class is not needed, but I would still recommend it if only to enable a proper styling.
Based on the question here, you could access the relevant item and change attributes like so:
var ds = $('#YourCombo').data().kendoComboBox.dataSource;
//someIndex is the index of the item in the dataSource
ds.data()[someIndex].set("enabled","False");
Kendo currently doesn't support such functionality but this is easiest hack I found to disable an option in Kendo Dropdown.
$("#" + id + "_listbox .k-item")[index].disabled = true;
where id -> ID of your dropdown
index -> position of the element in the dropdown you want to disable.
Hope it helps. Enjoy :)
You could try something like this:
var dropDown = $("#yourDropdown").data("kendoDropDownList");
dropDown.enable(false);
Try other way for specific index
var dropDown = $("#color").data("kendoDropDownList");
$("#color" + "_listbox .k-item")[index].disabled = true;
$("#color" + "_listbox .k-item").eq(index).addClass("tbd");
Fiddler for reference :- http://jsfiddle.net/xLs4n9dm/2/
If you want to disable the entire control and you are using the MVC fluent API, then you can use the .HtmlAttributes() method:
#Html.Kendo()
.DropDownList()
.HtmlAttributes(new { #disabled = "disabled" })
Try like this
$('#YourDropDown').attr('disabled', 'disabled');