Angular2 model binding not working - data-binding

I try to update an input value in Angular 2, it works the first time the size value exceeds maxSize, but afterwords it does not work anymore. It seems like when I am setting this.size to some value the UI is not updated, am I overlooking something ?
HTML:
<input type="text" class="form-control" [value]="size" (input)="size = updateValue($event)">
Code:
export class BrushSizePicker {
#Input() minValue;
#Input() maxValue;
#Input() size;
increaseValue(event) {
this.size++;
this.checkValue();
}
decreaseValue(event) {
this.size--;
this.checkValue();
}
updateValue(event) {
this.size = parseInt(event.target.value);
this.checkValue();
return this.size;
}
private checkValue() {
if (this.size > this.maxValue) {
this.size = this.maxValue;
}
if (this.size < this.minValue) {
this.size = this.minValue;
}
}
EDIT:
I logged what happened: checkValue is called every time with the correct input, and it returns the correct value. But the new value is not set into the input field / value field

While it may not solve the problem, the way you have implemented the input event can be simplified. I would have written it like this, side-effect free functions:
updateValue(event) { // The method name with this change is a misnomer
return this.checkValue(parseInt(event.target.value));
}
private checkValue(item) {
if (item > this.maxValue) {
return this.maxValue;
}
else if (else < this.minValue) {
return this.maxValue;
}
return item;
}

Related

TornadoFX:proper way to bind model

I was taking a look at this :
tornadofx
and tried to expand on it with database connection and little more options, (not all of them make sense, but its just playing in a sandbox).
Even though table can be directly edited and the data will persist in database, i did try to do edit through text fields too. actual table editing would happen through different view and not table itself, as i said its just example.
Database used is Jetbrains Exposed.
object Categories : IntIdTable() {
val name = varchar("name", 64).uniqueIndex()
val description = varchar("description", 128)
}
class Category(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Category>(Categories)
var name by Categories.name
var description by Categories.description
override fun toString(): String {
return "Category(name=\"$name\", description=\"$description\")"
}
}
now controller looks something like this, functions are just rudimentary and picked as an example.
typealias ModelToDirtyState = Map.Entry<CategoryModel, TableColumnDirtyState<CategoryModel>>
class CategoryModel() : ItemViewModel<Category>() {
val name: SimpleStringProperty = bind(Category::name)
val description: SimpleStringProperty = bind(Category::description)
}
class DBController : Controller() {
val categories: ObservableList<CategoryModel> by lazy {
transaction {
SchemaUtils.create(Categories)
Category.all().map {
CategoryModel().apply {
item = it
}
}.observable()
}
}
init {
Database.connect(
"jdbc:mysql://localhost:3306/test", driver = "com.mysql.cj.jdbc.Driver",
user = "test", password = "test"
)
TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
}
fun deleteCategory(model: CategoryModel) {
runAsync {
transaction {
model.item.delete()
}
}
categories.remove(model)
}
fun updateCategory(model: CategoryModel) {
transaction {
Categories.update {
model.commit()
}
}
}
fun commitDirty(modelDirtyMappings: Sequence<ModelToDirtyState>) {
transaction {
modelDirtyMappings.filter { it.value.isDirty }.forEach {
it.key.commit()
println(it.key)// commit value to database
it.value.commit() // clear dirty state
}
}
}
Just to quickly comment on controller, delete method works as "intended" however the update one does not, it does not work in sense that after using delete item is remove both from database and tableview(underlying list) itself, and when i do update its not, now i know the reason, i call remove manually on both database and list, now for update perhaps i could do change listener, or maybe tornadofx can do this for me, i just cant set it up to do it. Following code will make things clearer i think.
class CategoryEditor : View("Categories") {
val categoryModel: CategoryModel by inject()
val dbController: DBController by inject()
var categoryTable: TableViewEditModel<CategoryModel> by singleAssign()
var categories: ObservableList<CategoryModel> by singleAssign()
override val root = borderpane {
categories = dbController.categories
center = vbox {
buttonbar {
button("Commit") {
action {
dbController.commitDirty(categoryTable.items.asSequence())
}
}
button("Roll;back") {
action {
categoryTable.rollback()
}
}
// This model only works when i use categorytable.tableview.selected item, if i use categoryModel, list gets updated but not the view itself
// Question #1 how to use just categoryModel variable without need to use categorytable.tableview.selecteditem
button("Delete ") {
action {
val model = categoryTable.tableView.selectedItem
when (model) {
null -> return#action
else -> dbController.deleteCategory(model)
}
}
}
//And here no matter what i did i could not make the view update
button("Update") {
action {
when (categoryModel) {
null -> return#action
else -> dbController.updateCategory(categoryModel)
}
categoryTable.tableView.refresh()
}
}
}
tableview<CategoryModel> {
categoryTable = editModel
items = categories
enableCellEditing()
enableDirtyTracking()
onUserSelect() {
//open a dialog
}
//DOES WORK
categoryModel.rebindOnChange(this) { selectedItem ->
item = selectedItem?.item ?: CategoryModel().item
}
// Question #2. why bindSelected does not work, and i have to do it like above
//DOES NOT WORK
// bindSelected(categoryModel)
//
column("Name", CategoryModel::name).makeEditable()
column("Description", CategoryModel::description).makeEditable()
}
}
right = form {
fieldset {
field("Name") {
textfield(categoryModel.name)
}
}
fieldset {
field("Description") {
textfield(categoryModel.description)
}
}
button("ADD CATEGORY") {
action {
dbController.addCategory(categoryModel.name.value, categoryModel.description.value)
}
}
}
}
}
I apologize for huge amount of code, also in last code snipped i left questions in form of comments where i fail to achive desired results.
I am sure i am not properly binding code, i just dont see why, also i sometimes use one variable to update data, my declared one "categoryModel" and sometimes i use tableview.selecteditem, it just seems hacky and i cant seem to grasp way.
Thank you!

Angular2: Set CSS class from Observable

There are actually two challenges I face. I am looping through an array of values and need to
set a class name depending on an observable variable from a child
component.
reevaluate the class as soon as the child variable changes.
location.component.ts
import { Component, Input } from '#angular/core';
import { BusinessLocation, SpecialDays, RegularHours } from './location';
import { DbService } from './db-service.component';
#Component({
selector: '[data-locations]',
templateUrl: 'app/location.component.html',
providers: [DbService]
})
export class LocationComponent {
locations:BusinessLocation[];
selectedLocationId:Number;
constructor(private api:DbService){}
isOpenOnDay(day):Boolean {
let _weekDay = day.getDay();
let _retour = false;
this.locations.forEach(loc => {
if ( loc.id == this.selectedLocationId && loc.regularHours.weekDay == _weekDay ) {
_retour = true;
}
});
this.locations.forEach(loc => {
if ( loc.id == this.selectedLocationId && loc.specialDays.singleDate.getDay() == _weekDay) {
_retour = true;
}
});
return _retour;
}
getLocation():Number {
return this.selectedLocationId;
}
setLocation(id):void {
this.selectedLocationId = id;
}
getLocations():void {
this.api.getLocations().subscribe(
locations => {
this.locations = locations as BusinessLocation[];
this.setLocation(this.locations[0].id);
}
);
}
}
a snippet from db-services.component.ts
getLocations():Observable<BusinessLocation[]> {
return this.http.get(this.apiUrl + '/get_locations.php')
.map(response => response.json().data as BusinessLocation[]);
}
}
it all works fine. However, here is where the challenge is. The parent component initiates the locations, but it also needs to know what location is selected right now. Here is the month.component.html
<span class="location-container" #location data-locations><span class="loading">Loading locations...</span></span>
<div *ngFor="let day of week.days" class="day" data-can-drop="day"
[class.today]="isToday(day)"
[class.in-other-month]="day.getMonth() != jsMonth"
[class.is-closed]="!isOpenAtLocation(day)">
<div class="day-marker"></div>
<span class="day-date">{{day | date:'d'}}</span>
<span *ngIf="checkMonth(day)" class="day-month">{{months[day.getMonth()]}}</span>
</div>
and a snippet from month.component.ts is
#ViewChild('location') locationComponent:LocationComponent;
isOpenAtLocation(day):Boolean {
return this.locationComponent.isOpenOnDay(day);
}
ngOnInit(): void {
this.locationComponent.getLocations();
}
The error I get is pretty straightforward and totally understandable:
Subscriber.ts:238 TypeError: Cannot read property 'forEach' of undefined
at LocationComponent.isOpenOnDay (location.component.ts:25)
at MonthComponent.isOpenAtLocation (month.component.ts:176)
And this is just about Challenge 1. The Challenge 2 has not been even addressed yet.
I just can't wrap my head around it. >_<
Thanks.
Well, this was a bad joke on my side. First, this is a reminder, that a change of a property of an object will reflect itself in DOM if there is a binding available. So going with [class.isOpenOnDay]="day.isOpenAtLocation" would be totally sufficient, where day is an object and isOpenAtLocation is its property. Even if it is not set initially (meaning it is null) and will be updated in the future – it is all good. This is basically how NG works (and has worked all the time). Silly me.
The other problem – changing the value depending on a child component variable – has been solved by emitting events (from child), listening to the events (in parent) and resetting the property isOpenAtLocation again.
So the updated child component location.component.ts has been updated like this:
#Output() locationChanged = new EventEmitter<Number>();
setLocation(id):void {
this.selectedLocationId = id;
this.locationChanged.emit(this.selectedLocationId);
}
The view for the location component now has this line:
<select (change)="setLocation($event.target.value)">
<option *ngFor="let loc of locations" value="{{loc.id}}">{{loc.longName}}</option>
</select>
The parent component's view is bound to the event like this:
<span class="location-container" #location data-locations (locationChanged)="onLocationChange($event)"><span class="loading">Loading locations...</span></span>
And the parent month.component.ts itself has two more methods:
onLocationChange(event) {
if ( this.selectedLocationId != event ) {
this.selectedLocationId = event;
this.setLocation();
this.dispatchResize();
}
}
setLocation():void {
if ( this.selectedLocationId >= 0) {
for ( let i = 0; i < this.weeks.length; i++) {
let _week = this.weeks[i];
_week.forEach(_day => {
let _isOpen = this.locationComponent.isOpenOnDay(_day.date);
_day['isOpenOnDay'] = _isOpen.isOpenOnDay;
_day['isSpecialDay'] = _isOpen.isSpecialDay;
_day['dayHours'] = _isOpen.dayHours;
});
}
}
}
As one can see, I have added even more dynamically checked properties, not just isOpenOnDay, but also isSpecialDay and dayHours, which are not yet defined initially, but are set as soon as the data is available – and are reflected in view as soon as they change.
Basic stuff, actually. Still might be helpful to some NG2 noob like me.

Invalid grouped property access

In this qml code:
Component {
id: userdelegate
PictureBox {
...
icon: model.icon
icon.heigth: 50
}
}
PictureBox comes from the PictureBox.qml system file in this way:
...
Image {
id: icon
...
width: parent.width; height: 150
}
Running qml, I have the error in the title.
I need to use PictureBox.qml, but I can't change it.
How can I override default height value for PictureBox.qml icon?
You can try to bypass QML's scoping rules by traversing Item children until you can find the Image and manipulate it directly. It's possible it could break in the future, but item.toString() gives you something useful:
item.toString() -> "QQuickImage(0x114054350)"
So, you can try something like this (not tested):
function findItemOfType(type, item) {
if (item.toString().indexOf(type) != -1) {
return child;
}
for (var i=0;i < children.length;++i) {
var child = children[i];
var result = findItemOfType(type, child.children);
if (result != null) {
return result;
}
}
return null;
}
Using it like this:
findItemOfType("QQuickImage", pictureBoxId);

incrementing a $_GET variable

How do I get this $_GET['ago'] variable to increment by 1
public function __construct($future=false) {
$GLOBALS['twd_helper']=&$this;
}
public function query_string($add, $remove=null) {
$qs_data = array();
parse_str($_SERVER['QUERY_STRING'], $qs_data);
$qs_data = array_merge($qs_data, $add);
if ($remove) {
foreach($remove as $key) {
if (isset($qs_data[$key])) {
unset($qs_data[$key]);
}
}
}
return http_build_query($qs_data);
}
public function filter_overnight () {
if (isset($_GET['tod']) && $_GET['tod'] == 'overnight') {
$overnight = $_GET['overnight'];
}
if (($_GET['ago'])) {
$_GET['ago'] ? $_GET['ago']++ : $_GET['ago'] = 0;
}
}
I want to change my URL to increase by 1 each time its clicked
guide?ago=1
guide?ago=2
guide?ago=3
etc
In the html, just do this:
Link Text
Your filter_overnight function is a bit odd too - I think it could be made more readable:
if ($_GET['ago'])
{
$_GET['ago'] = (int)$_GET['ago'] + 1;
}
else
{
$_GET['ago'] = 0;
}
Or with the ternary:
$_GET['ago'] = ($_GET['ago']) ? (int)$_GET['ago'] + 1 : 0
Without seeing the whole code, I'm not sure why filter_overnight is incrementing $_GET['ago'], but at a glance it appears to put an undocumented side effect into the function by changing a global value (which would also render the increment in the <a> pointless). The superglobals in PHP can be dangerous things - I find it best to treat them as immutable and copy values out of them vs working on them directly.

flex select value from Combo

My goal is to create a generic function that selects a value in a combobox accoring to a value.
(My comoBox holds arrayCollection as dataProvider.)
The difficulty is infact to get a propertyname in runtime mode
public function selectComboByLabel(combo:ComboBox , propetryName:String, value:String):void {
var dp:ArrayCollection = combo.dataProvider as ArrayCollection;
for (var i:int=0;i<dp.length;i++) {
if (dp.getItemAt(i).propertyName==value) {
combo.selectedIndex = i;
return;
}
}
}
the line if (dp.getItemAt(i).propertyName==value)
is of course incorrect.
It should be arther something like: dp.getItemAt(i).getPropertyByName(propertyName)
Any clue on how to that ?
Don't use Object Property notation. Do this:
dp.getItemAt(i)[propertyName]
In addition to what Flextras said, you could also redo your for loop to make it easier to read:
for each(var item:Object in dp) {
if(item[propertyName] == value) {
combo.selectedItem = item;
return;
}
}

Resources