Angular array values connected to ng-model - javascript

I'm using two <select> tags to push values to an array in the $scope. For some reason this array then becomes connected to the select elements and when they are changed it changes the array elements.
I have made a codepen to demonstrate this behaviour.
View:
<div class="form-group">
<div class="row">
<div class="col-sm-4">
<label class="item item-input item-select">
<div class="input-label positive">
Select Parameter
</div>
<select ng-model="data.param">
<option ng-repeat="param in params track by $index" value="{{param}}">{{param}}</option>
</select>
</label>
</div>
<div class="col-sm-4">
<label class="item item-input item-select">
<div class="input-label positive">
{{data.param || 'SELECT'}}
</div>
<select ng-model="data.childParam">
<option ng-repeat="child in children[data.param] track by $index" value="{{child}}">{{child}}</option>
</select>
</label>
</div>
<div class="col-sm-4">
<button class="btn btn-primary" ng-click="addParam(data)"> SAVE</button>
</div>
</div>
<ul class="list-group">
<li ng-repeat="savedParm in activeExercise.Params track by $index" class="list-group-item"><strong>{{savedParm.param}}</strong> : {{savedParm.childParam}}
</li>
</ul>
{{activeExercise.Params}}
</div>
Controller:
$scope.addParam = function(data) {
console.log(data);
if (!$scope.activeExercise.Params) {
$scope.activeExercise.Params = [];
}
if ($scope.activeExercise) {
$scope.activeExercise.Params.push(data);
} else if ($scope.editExercise.Params) {
$scope.editExercise.Params.push(data);
}
console.log(JSON.stringify($scope.activeExercise));
}

You add to array reference to object, so when you change value in select, you change property existing object, but not object reference, so all reference point to object with updated field.
for solving you can copy object fields to new object, like:
var ndata = {param:data.param, childParam:data.childParam}
and then push to array ndata object instead data

Related

Knockoutjs: how can I make the input value added to a list also observable

Not sure if I am phrasing this correctly.
I have an observableArray and I can add to that array from an input and also remove the list item. but if I modify the created item I lose the connection to the array. How can I keep the binding to the array?
Fiddle Attached
HTML
<div class="group-settings-container mt-4">
<div class="row">
<div class="col-md-3">
<h4><i class="fas fa-object-group"></i> Create Groups</h4>
</div>
<div class="col-md-6">
<div class="input-group">
<input type="text" class="form-control create-group-name" data-bind="value: groupItemToAdd, valueUpdate: 'afterkeydown' " placeholder="Enter group name" value="">
<div class="input-group-append">
<button class="btn btn-primary add-group-btn" data-bind="click: addGroupItem, enable: groupItemToAdd().length > 0" type="button"><i class="fas fa-plus"></i>
Add group</button>
</div>
</div>
</div>
<div class="create-groups-container mb-4">
<ul class="list-group create-group-list my-2" data-bind="foreach: allGroupItems">
<li class="list-group-item">
<div class="input-group">
<input type="text" class="form-control created-group-input" data-bind="value: $data">
<div>
<button class="btn btn-danger remove-group-item-btn" data-bind="click: $parent.removeSelectedGroupItem" type="button"><i class="fas fa-times"></i>
Remove</button>
</div>
</div>
</li>
</ul>
</div>
<!-- end create groups container -->
</div>
<!-- end group settings container -->
JS
function ViewModel() {
var self = this;
self.groupItemToAdd = ko.observable("");
self.allGroupItems = ko.observableArray([]);
self.addGroupItem = function() {
if ((self.groupItemToAdd() != "") && (self.allGroupItems.indexOf(self.groupItemToAdd()) < 0)) {
self.allGroupItems.push(self.groupItemToAdd());
}
self.groupItemToAdd(""); // clear the input
}
self.removeSelectedGroupItem = function(index) {
// self.allGroupItems.splice(index, 1);
// console.log(self.allGroupItems.splice(index, 1));
self.allGroupItems.remove(index);
}
}
// end ViewModel
ko.applyBindings(new ViewModel());
You have an observableArray. Which means, any changes to the array are tracked and updated. The items inside it are just strings. They are not observables. Any changes you make from the UI is not updated back to the view model. This behaviour is not limited to strings. The same thing applies if you have an observableArray of regular javascript object literals.
From the documentation:
Simply putting an object into an observableArray doesn’t make all of that object’s properties themselves observable. Of course, you can make those properties observable if you wish, but that’s an independent choice. An observableArray just tracks which objects it holds, and notifies listeners when objects are added or removed.
So, instead of adding strings to the observableArray, you can push an object with an observable property to the observableArray. Now the changes to item property are tracked. It's important to make the property an observable, otherwise you'll run into the same issue.
function ViewModel() {
var self = this;
self.groupItemToAdd = ko.observable("");
self.allGroupItems = ko.observableArray([]);
self.addGroupItem = function() {
if (self.groupItemToAdd() && !self.allGroupItems().some(a => a.item() === self.groupItemToAdd())) {
self.allGroupItems.push({
item: ko.observable(self.groupItemToAdd())
});
}
self.groupItemToAdd(""); // clear the input
}
self.removeSelectedGroupItem = function(index) {
self.allGroupItems.remove(index);
}
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
<div class="group-settings-container mt-4">
<div class="row">
<div class="col-md-3">
<h4><i class="fas fa-object-group"></i> Create Groups</h4>
</div>
<div class="col-md-6">
<div class="input-group">
<input type="text" class="form-control" data-bind="value: groupItemToAdd, valueUpdate: 'afterkeydown' " placeholder="Enter group name">
<div class="input-group-append">
<button class="btn btn-primary add-group-btn" data-bind="click: addGroupItem, enable: groupItemToAdd().length > 0" type="button">Add group</button>
</div>
</div>
</div>
<ul class="list-group create-group-list my-2" data-bind="foreach: allGroupItems">
<li class="list-group-item">
<div class="input-group">
<input type="text" class="form-control created-group-input" data-bind="value: item">
<div>
<button class="btn btn-danger remove-group-item-btn" data-bind="click: $parent.removeSelectedGroupItem" type="button">Remove</button>
</div>
</div>
</li>
</ul>
</div>
<span data-bind="text: allGroupItems().map(a => a.item())"></span>
Note:
You need to change the input binding inside foreach from $data to item (observable property name)
To check if a group item is already added, use some like this: self.allGroupItems().some(a => a.item() === self.groupItemToAdd())
The last span demonstrates that the observable is updated

jQuery Datatable - I keep getting undefined when i call a row data from a json datatable

Im currently developing a project at my job, im using Laravel as my framework and im using KeenThemes as my frontend. I believe they have their variation of the Datatables library and maybe that's why im having this issue. Since i can't find a well documented example of the metronic Datatable library im using the original Datatable documentation to work on this project. Ok, so on to the problem. This is my blade component for the datatable.
<div class="m-portlet m-portlet--mobile m-portlet--rounded">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
{{$title}}
</div>
</div>
<div class="m-portlet__head-tools">
{{$buttons}}
<ul class="m-portlet__nav">
<li class="m-portlet__nav-item">
<div class="m-dropdown m-dropdown--inline m-dropdown--arrow m-dropdown--align-right m-dropdown--align-push" m-dropdown-toggle="hover"
aria-expanded="true">
<a href="#" class="m-portlet__nav-link btn btn-lg btn-secondary m-btn m-btn--icon m-btn--icon-only m-btn--pill m-dropdown__toggle">
<i class="la la-ellipsis-h m--font-brand"></i>
</a>
<div class="m-dropdown__wrapper">
<span class="m-dropdown__arrow m-dropdown__arrow--right m-dropdown__arrow--adjust"></span>
<div class="m-dropdown__inner">
<div class="m-dropdown__body">
<div class="m-dropdown__content">
<ul class="m-nav">
<li class="m-nav__section m-nav__section--first">
<span class="m-nav__section-text">Acciones</span>
</li>
{{$actions}}
<li class="m-nav__separator m-nav__separator--fit m--hide">
</li>
<li class="m-nav__item m--hide">
Submit
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
<div class="m-portlet__body">
{{-- begin - search input --}}
<div class="m-form m-form--label-align-right m--margin-top-20 m--margin-bottom-30">
<div class="row align-items-center">
<div class="col-xl-8 order-2 order-xl-1">
<div class="form-group m-form__group row align-items-center">
<div class="col-md-4">
<div class="m-form__group m-form__group--inline">
<div class="m-form__label">
<label>Status:</label>
</div>
<div class="m-form__control">
<select class="form-control m-bootstrap-select" id="m_form_estado">
<option value="">All</option>
<option value="1">En servicio</option>
<option value="6">En Busqueda</option>
<option value="5">En Saneamiento</option>
<option value="3">En Obra</option>
<option value="2">En Instalación</option>
<option value="4">Listo Para Ejecutar</option>
<option value="12">Sin Estado</option>
<option value="14">Caido</option>
<option value="15">Retirado</option>
</select>
</div>
</div>
<div class="d-md-none m--margin-bottom-10"></div>
</div>
<div class="col-md-4">
<div class="m-form__group m-form__group--inline">
<div class="m-form__label">
<label class="m-label m-label--single">Type:</label>
</div>
<div class="m-form__control">
<select class="form-control m-bootstrap-select" id="m_form_type">
<option value="">All</option>
<option value="1">Online</option>
<option value="2">Retail</option>
<option value="3">Direct</option>
</select>
</div>
</div>
<div class="d-md-none m--margin-bottom-10"></div>
</div>
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<input type="text" class="form-control m-input" placeholder="Search..." id="generalSearch">
<span class="m-input-icon__icon m-input-icon__icon--left">
<span><i class="la la-search"></i></span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- end - search input --}}
<!--begin: Datatable -->
<div class="m_datatable" id="m_datatable"></div>
<!--end: Datatable -->
</div>
Now, this is some of the javascript im using to render and fill the table with data. The part im gonna paste is where i define the fuction to search data and also the definition of the column i wanna get from the row.
{
field: "IdEstacion",
title: "#",
width: 40,
sortable: !0,
selector: !1,
textAlign: "center",
responsive: {
hidden: 'lg'
},
template: '{{IdEstacion}}',
query: {},
sort: {
sort: "asc",
field: "IdEstacion"
}
{...}
$("#m_datatable").on("click", "tr", function () {
var tr=$(this).parents("tr")[0];
var row=t.row(row).data();
console.log(row));
alert(row);
})
I've checked the console and also checked the values in the debug and row is returning an object, however, it is returning the whole datatable not just the row i clicked on. And when I try to reference a value from the row variable i keep getting undefined on the console and on the alert. Am i missing something? Thanks in advance.
EDIT: adding my json structure
JSON value #1
$('#YourTable tbody').on( 'click', 'a', function () {
var data = '';
data = YourTable.row( $(this).parents('tr') ).data();
//to do this your table need to be declared like this
//yourTable= $('#YourTable').DataTable();
console.log(data);
var carId= data['id'];
console.log(carId);
})
The idea is right, but there seem to be a few typos in your example.
var tr=$(this).parents("tr")[0] is not needed, change row to this in var row=t.row(row).data();
There's a syntax problem in console.log(row));
Also, t needs to be defined.
var t = $("#m_datatable").DataTable();
t.on("click", "tr", function () {
var row = t.row(this).data();
console.log(row);
alert(row);
})

When are two selects on same page works only one(the second)

If I add two select on the same page, in the first select not working ng-selected, only in the second select.
If I remove in the first select
ng-model="opcionSeleccionadaInicio"` ng-change="vm.modificarHoraInicio($index,opcionSeleccionadaInicio)"
The ng-selected works, but I don't have any model and I can't handle when the user change the select.
This is my code:
<ion-view view-title="Cambiar horarios">
<ion-content>
<div class="list card" ng-repeat="dia in vm.dias">
<div class="item item-divider" align="center">
{{dia.nombre}}
</div>
<div class="list">
<label class="item item-input item-select">
<div class="input-label">
Primer turno
</div>
<select ng-model="opcionSeleccionadaInicio" ng-change="vm.modificarHoraInicio($index,opcionSeleccionadaInicio)">
<option ng-repeat="horario in dia.horarios" ng-selected="dia.valorInicio == horario">{{horario}}</option>
</select>
</label>
<label class="item item-input item-select">
<div class="input-label">
Último turno
</div>
<select ng-model="opcionSeleccionadaFin" ng-change="vm.modificarHoraFin($index,opcionSeleccionadaFin)">
<option ng-selected="dia.valorFin == horario" ng-repeat="horario in dia.horarios" >{{horario}}</option>
</select>
</label>
</div>
</div>
<div class="padding-horizontal"><button class="button button-block button-positive" ng-click="vm.guardarHorarios()"> Guardar horarios </button></div>
</ion-content>
</ion-view>
The first select show me the options, but not select a default value like the second select.
Thanks everyone for helping!
I looked at your code, we can use two ng-selected, just set their values different.
first: <option ng-repeat="horario in dia.horarios" ng-selected="dia.valorInicio == horario">{{horario}}</option>
second: <option ng-selected="dia.valorFin == horario" ng-repeat="horario in dia.horarios" >{{horario}}</option>
Check this link for further help:
https://plnkr.co/edit/3aH1dlJD43HUU2ZBuMXU?p=preview
I found the problem! There is not need to define a ng-selected, just define ng-model with the value what you want to modify. Also the option choose by default the value of the model.
This is my final code:
<ion-view view-title="Cambiar horarios">
<ion-content>
<div class="list card" ng-repeat="dia in vm.dias">
<div class="item item-divider" align="center">
{{dia.nombre}}
</div>
<div class="list">
<label class="item item-input item-select">
<div class="input-label">
Primer turno
</div>
<select ng-model="dia.valorInicio" ng-change="vm.modificarHoraInicio($index,dia.valorInicio)">
<option ng-repeat="horario in dia.horarios">{{horario}}</option>
</select>
</label>
<label class="item item-input item-select">
<div class="input-label">
Último turno
</div>
<select ng-model="dia.valorFin" ng-change="vm.modificarHoraFin($index,dia.valorInicio)">
<option ng-repeat="horario in dia.horarios" >{{horario}}</option>
</select>
</label>
</div>
</div>
<div class="padding-horizontal"><button class="button button-block button-positive" ng-click="vm.guardarHorarios()"> Guardar horarios </button></div>
</ion-content>
</ion-view>
// EDIT //
Also, If someone use ng-options is the same:
<select ng-model="dia.valorInicio" ng-options="horario for horario in dia.horarios" ng-change="vm.modificarHoraInicio($index, dia.valorInicio)"></select>
<select ng-model="dia.valorFin" ng-options="horario for horario in dia.horarios" ng-change="vm.modificarHoraFin($index, dia.valorFin)"></select>
Cheers!

Scoping issue when adding multiple instances of views using ng-repeat

I'm hitting a scoping issue when using the ng-repeat functionality of AngularJS.
please see the plnkr
I have an array of objects 'boxCollection' and a list of items 'itemCollection' which I display in a drop down.
$scope.boxCollection = [];
$scope.itemCollection =
[
{name: 'item1'},
{name: 'item2'},
{name: 'item3'}
];
Now I have my view as
<script type="text/ng-template" id="addBox.html">
<div class="box-controls">
<span class="glyphicon glyphicon-plus pull-left" ng-click="addBox()"></span>
<span class="glyphicon glyphicon-minus pull-left" ng-class="{disable_div:boxCollection.length < 2} " ng-click="removeBox($index)"></span>
</div>
<div class="box-container">
<div class="box-details">
<div class="boxItem">
<form class="form-horizontal">
<div class="form-group">
<label class="control-label col-md-3">Item</label>
<div class="col-md-8">
<select class="form-control" ng-options="item.name for item in itemCollection" ng-model="boxCollection[$index].item" ng-disabled="false">
<option value=""> None </option>
</select>
</div>
</div>
<div class=" form-group ">
<label class="control-label col-md-3">Item Desc</label>
<div class="col-md-8">
<input type="text " class="form-control " ng-model="boxCollection[$index].item.desc ">
</div>
</div>
</form>
</div>
</div>
<div class="clearfix "></div>
</div>
</script>
The view is wrapped in a script tag with an id and is called with an ng-repeat.
I have one function to add a box element into my view 'addBox()'. It generates one entry in 'boxCollection' array and another entry in 'boxTmplList' array. 'boxTmplList' is responsible for showing the views.
Now if you select let's say 'item1' from the drop down in box1 and add a value in the input field, add another box in the view using the '+' button and select 'item1' again in the another instance. It displays the value of input field 1 in the input field 2.
enter image description here
On further analysis I found that Angular tracks the objects with similar 'item' name using the same $hashkey.
I'm using a workaround by adding another property to the object 'boxCollection[$index].itemDesc' instead of 'boxCollection[$index].item.desc' and then later on modify the object using another function, but I feel that's not the most efficient way.
Any insight on this would be greatly appreciated.
You need to change ng-model="boxCollection[$index].item" to ng-model="boxCollection[$index].item.name" , like as-
<select class="form-control" ng-options="item.name for item in itemCollection" ng-model="boxCollection[$index].item.name" ng-disabled="false">
Working Plnkr
Change ng-model="boxCollection[$index].item.desc" to ng-model="itemCollection[$index].name".
<div class=" form-group ">
<label class="control-label col-md-3">Item Desc</label>
<div class="col-md-8">
<input type="text " class="form-control " ng-model="itemCollection[$index].name">
</div>
</div>

Jquery - Cloning collapsible divs

So im trying to build the front end of a form builder however i've run into a problem when trying to clone collapsible elements. Im an absolute beginner at jquery so sorry for the dirty code.
When an element is cloned and it is nested under itself with a modified sortable jquery plugin, any one of the bottom levels collapsible's will open the collapsible of the top level parent (but not any of the other nested elements).
My jquery is:
var count = 1;
$("#displayUpload").click(function() {
var clonecount = 'Uploadpanel' + count;
var cloneobject = $("#toggleUpload").clone(true).appendTo(".sortable").show();
$("#Uploadpanel", cloneobject).attr('id', clonecount);
$(cloneobject, this.id).on("click", (".edit", ".panel-title"), function() {
$("#" + clonecount, this.id).collapse('toggle');
});
$(cloneobject).on("click", ".remove", function() {
$(cloneobject).remove();
});
count = count + 1;
});
Html:
<li id="toggleUpload" data-role="main" class="js-form-element" style="display: none">
<div>
<div class="panel panel-default" data-role="collapsible" id="panel3">
<div class="panel-heading">
<h4 class="panel-title" data-target="#Uploadpanel"
href="#Uploadpanel">
<a data-target="#Uploadpanel"
href="#Uploadpanel" class="edit">
File upload
</a>
<span class="btn-group pull-right">
<a data-target="#Uploadpanel"
href="#Uploadpanel" class="collapsed">Edit</a>
<span class="padding-line">|</span>
<a class="remove" href="#">Delete</a>
</span>
</h4>
</div>
<div id="Uploadpanel" class="panel-collapse collapse">
<div class="panel-body">
<div class="margin-bottom-20">
<p class="font-italic">Label:</p>
<input type="text" class="form-control" id="exampleInputName2" placeholder="Label">
</div>
<div class="margin-bottom-20">
<p class="font-italic">Placeholder text:</p>
<input type="text" class="form-control" placeholder="Placeholder message">
</div>
<div class="margin-bottom-5">
<div class="checkbox">
<label>
<input id="required" type="checkbox"> Required
</label>
</div>
</div>
<div class="margin-bottom-20">
<p class="font-italic">Validation message:</p>
<input type="text" class="form-control" id="validation" placeholder="Validation message">
</div>
<div>
<div class="row">
<div class="col-sm-6">
<p class="font-italic">Form input size:</p>
<div class="form-group">
<select name="form_select">
<option value="0">100% (1 in a row)</option>
<option value="0">33% (3 in a row)</option>
<option value="0">50% (2 in a row)</option>
</select>
</div>
</div>
<div class="col-sm-6">
<p class="font-italic">Form position:</p>
<div class="form-group">
<select name="form_select">
<option value="0">Left</option>
<option value="0">Middle</option>
<option value="0">Right</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
Any help would be great,
Thanks
There can be some issues using clone on objects that already has event handlers. I had this issue for a while, and I just resolved it by creating a function that generates a new jQuery object, instead of cloning an existing one.

Categories