How to update Angular scope properties when they are passed into directives? - angularjs

I have functionality where developers can add custom Angular views where they can bind properties to the $scope.settings object. When clicking on the save button the $scope.settings object will be converted to JSON and saved to the database. Something like this will be the result:
{
"name": "bob",
"age": "25"
}
As long as I add elements like <input type="text" ng-model="settings.name" /> everything goes as expected.
But, now I want to add directives like this:
<umb-property property="property in properties">
<umb-editor model="property"></umb-editor>
</umb-property>
With the following code:
$scope.properties = [
{
label: 'Name',
alias: 'name',
view: 'textbox',
value: $scope.settings.name
},
{
label: 'Age',
alias: 'age',
view: 'number',
value: $scope.settings.age
}
];
The 'editor' directive loads views in place based on the 'view' property. The views are third party. The editors are loaded in a dialog. After submission of the settings dialog, the following line of code will convert the settings to JSON:
$scope.dialog = {
submit: function (model) {
var settingsJson = JSON.stringify(model.settings);
},
close: function (oldModel) {
//
}
};
In this case I cannot parse the $scope.settings to JSON, because the $scope.settings.name is not updated anymore. The $scope.editorModel.value was updated instead.
How can I bind the $scope.editorModel.value to $scope.settings.name?
I don't want to end up with a solution where I must update all $scope.settings values with the corresponding values from the editor models, because I want to support the dynamic way to convert the $scope.settings to a JSON value in the database.

For example I dont want to do: $scope.settings.name = $scope.properties[0].value
Use property accessors:
for (var i=0; i<$scope.properties.length; i++) {
$scope.settings[$scope.properties[i].alias] = $scope.properties[i].value;
};
HTML
<div ng-repeat="prop in properties">
<input ng-model="prop.value">
</div>

Related

VUEjs templates multiple selectboxes

So, I'm assigned to work with vue at work, but VUE and I aren't friends yet. Currently I'm facing an issue that I don't know how to resolve - I'll explain it the best I can with the limited VUE knowledge I possess.
Simplistically I have a vue component, which looks like this:
Vue.component('input-checkboxes', {
template: '#input_checkboxes',
props: ['id', 'label', 'list', 'required', 'readonly']
});
Then I have a template that looks like this:
<template id="input_checkboxes">
<div>
<div>{{ label }}</div>
<div>
<label v-for="list_item in list">
<input type="checkbox" v-model="value" :name="id" :required="required" :readonly="readonly" value="{{ list_item.name }}"> {{ list_item.name }}
</label>
</div>
</div>
</template>
Then I have a rather large vue instance that I'll paste the relevant parts of here.
This variable is being created:
var some_form = {
form : {
Endless: '',
Amounts: '',
Of: '',
Names: '',
In: '',
The: '',
Form: '',
THIS-ONE: ''
}
};
var vm = new Vue({
el: '#form_product',
data: $.extend({
someStuff : 'some values',
someLists : {}
}, some_form),
ready: function() {
this.getLists(); // Fetches alot of lists
},
methods: {
this.$http.get(
framework.url('api','getLookupLists')
).then(function (response) {
this.lists = response.body;
this.pageLoading = false;
}.bind(this));
}
In the end I have my html page that amongst loads of other fields, that works very well, has this:
<input-checkboxes
id="THIS-ONE"
label="A Fitting Label"
:value.sync="form.SomeID"
:list="lists.AnAppropriateList">
</input-checkboxes>
So, that's the gist of the setup. I have numerous other components like input-text, that works just fine (someone else made it before I did), I even created other components by copying his way, and just changing some elements.
I cannot get checkboxes to work, I think my problem is that there are numerous inputs, and that I don't know how to bind the results of those inputs to my VUE instance.
I really hope this makes sense, because I would really like some pointers on how to get on... Maybe if someone duplicated this setup really simplistic and showed how the array of values from the checkboxes could be bound to the vue instance?
There are a couple of mistakes you are (or might be) making.
First of all, the value prop you pass down has to be an array (seems
like it's a string from your example)
value is not correctly set, you need to set it by doing :value="someValue"; you can't have curlies in an attribute.
Lastly, value should probably be the id of the item and not the name. You have a chance of a collision if you use the name.
Bonus: you don't need to use :name at all (unless you are submitting the form server side...? But I can't see why you would do that.)
Here's a simple working example to sum this up:
HTML
<label v-for="list_item in list">
<input type="checkbox" v-model="value" :required="required" :readonly="readonly" :value="list_item.id"> {{ list_item.name }}
</label>
JS
var app = new Vue({
el: 'main',
data: function () {
return {
value: [],
label: 'Label name',
readonly: false,
required: true,
list: [
{
name: 'Item 1',
id: 'item1'
},
{
name: 'Item 2',
id: 'item2'
}
]
}
}
})
I've also made a bin for you to try it out.

AngularJS: refresh ng-options when property source object changes

Full description:
I have list of options with multiple properties (not just key-value pair):
[
{
id: 1,
name: "111",
some: "qqq"
},
{
id: 2,
name: "222",
some: "www"
}
];
And select defined as:
<select name="mySelect" id="mySelect"
ng-options="option.name for option in opts track by option.id"
ng-model="mod1"></select>
Depending on app logic, extra property may change:
{
id: 2,
name: "222",
some: "www1"
}
But actual list in Select doesn't change!
However, if name changes, then entire optionList will be refreshed.
In short you can see demo on JSFiddle OR JSFiddle. I prepared 2 very similar examples:
When button is clicked only extra property updates
When button is clicked - both extra property and key receive new value
Does anybody know solution?
UPDATE
For now I'm solving that issue with update + delay + update solution:
this.click = function(){
$scope.opts = {};
$timeout(function() {
$scope.opts = { /* NEW OBJECT */};
}, 0);
}
OK, so I think I understand what you want, which is to be able to select an option whose nested values may have changed since the list was rendered in the DOM.
Based on that understanding, I believe that the plunker I have created illustrates a solution for you. If you select one of the options, and change the child value in the input field, two-way binding will update the model.
Basically, it is taking the users selection, and on select change, re-assigning the selected object to reference the original option in the options array. This will allow two-way binding to occur. If you view the code, you will see that the input fields are updating the option list itself ($scope.options), where-as the model that is being displayed is $scope.formData.model.
https://plnkr.co/edit/DLhI7t7XBw9EbIezBCjI?p=preview
HTML
<select
name="mySelect"
id="mySelect"
ng-model="formData.model"
ng-change="onChange(formData.model)"
ng-options="option.name for option in options track by option.id"></select>
SELECTED CHILD: {{formData.model.child.name}}
<hr>
<div ng-repeat="option in options">
Child Name for {{ option.name }}: <input ng-model="option.child.name">
</div>
JS
$scope.onChange = function(option) {
angular.forEach($scope.options,function(optionItem){
if (optionItem.id == option.id){
$scope.formData.model = optionItem;
}
})
}
$scope.options = [
{
id: 1,
name: "111",
child: {
id: 11,
name: "111-1"
}
},
{
id: 2,
name: "222",
child: {
id: 22,
name: "222-1"
}
}
];
$scope.formData = {
model: $scope.options[0]
};
Call $apply whenever you want to apply changes made.
$scope.$apply();
This will tell AngularJS to refresh.

angular: return array from object array properties

I've got a 'user' object passed in when I load up a form, which I can use to directly populate my edit user form. So:
$scope.userData = getUserData();
<input id="uid" value="{{userData.uid}}"/>
<input id="name" value="{{userData.name}}"/>
My data looks like this:
userData = [{
"empId":1,
"name": "bob",
"roles":[{
"roleId":1,
"title":"boss"
},{
"roleId":2,
"title":"employee"
}]
}]
I've got a custom control that wants the roles as a FLAT array of titles.
So, this is what I'm hoping to produce:
<sys-multi id="roles" options="['boss','employee']"></sys-multi>
The trick here is that I'm hoping to do this IN THE TEMPLATE, so no functions so my controller. i.e. something like this (if it worked):
<sys-multi id="roles" options="{{userData.roles.name.join(',')}}"></sys-multi>
I've been playing with grep, but angular doesn't like grep within its {{}}'s.
How about adding a getter (or a function) to your userData object:
$scope.userData = {
"empId":1,
"name": "bob",
"roles":[{
"roleId":1,
"title":"boss"
},{
"roleId":2,
"title":"employee"
}],
get roleNames() {
return this.roles.map(function (role) {
return role.title;
});
}
};
...and then using it this way:
<sys-multi id="roles" options="userData.roleNames"></sys-multi>
http://codepen.io/jlowcs/pen/emPKrB

UI Grid - Formatting Data

I'm using the new Angular UI Grid (that is planned to replace ng-grid).
My data needs some formatting before it's displayed in the table. For instance, my server returns an attribute named "status" as a number, but I want to display it as a nice name.
If status=1 display "Todo", if status=2 display "Doing" etc.
How can this be done in UI Grid?
The preferred method now is to use a cellFilter, rather than a custom template. Custom templates are OK, but they impose more workload on upgrade - you have to check whether new features require modifications to your template.
There is a reasonable example of filters in the tutorials: http://ui-grid.info/docs/#/tutorial/201_editable
Note the cellFilter: 'mapGender' on the gender column, and the filter itself defined further below in the tutorial:
.filter('mapGender', function() {
var genderHash = {
1: 'male',
2: 'female'
};
return function(input) {
if (!input){
return '';
} else {
return genderHash[input];
}
};
})
First step, add a cellTemplate to the column:
$scope.gridOptions.columnDefs = [
{field:'status', displayName: 'Status',cellTemplate: 'statusTemplate.html'}
];
The Template-File should look like this (COL_FIELD is the actual field):
<div style="text-align: center">{{COL_FIELD==1 ? 'Todo' : 'Doing'"}}</div>
Hope, you got the idea! :)
The shortest way is use CellTemplate with appScopeProvider:
vm.gridOptions = {
columnDefs: [
{
field: 'status',
cellTemplate: '<div>{{grid.appScope.formatStatus(row)}</div>'
}
],
appScopeProvider: {
formatStatus: function (row) {
return row.entity.status === 1 ? 'Todo' : 'Doing';
},
}
}

How to populate add-row form programmatically for Kendo UI grid (AngularJS)

I have an order line grid where I need to be able to open the popup editor form programmatically with the edit form fields pre-populated (using AngularJs).
In the HTML, I have a lineGrid and an addButton, which calls addRow() on the ticketEntryController:
<div id="wrapper" class="container-fluid" ng-controller="ticketEntryController">
<div ng-controller="ticketLineController">
<div kendo-grid="ticketLineGrid" k-options="getTicketLineGridOptions()"></div>
</div>
<button id="addButton" ng-click="addRow()" class="btn btn-primary btn-sm">Add Row</button>
</div>
Here is the ticketEntryController:
(function () {
'use strict';
angular.module('app').controller('ticketEntryController', ticketEntryController);
function ticketEntryController($scope) {
$scope.lineGrid = {};
$scope.addRow = function () {
var item = { itemNo: 'TEST123', itemDescr: 'Some description' };
$scope.$broadcast('AddRow', item);
}
}
})();
Here is part of the ticketLineController:
function ticketLineController($scope) {
$scope.$on('AddRow', function(event, item) {
console.log("ticketLineController, AddRow: " + item.itemNo);
$scope.itemNo = item.itemNo;
$scope.itemDescr = item.itemDescr;
$scope.ticketLineGrid.addRow();
});
Plunker: http://plnkr.co/edit/VG39UlTpyjeTThpTi4Gf?p=preview
When the Add Row button is clicked, the editor popup form opens up, but all fields are empty. How can I populate the fields (like they are when you click the Edit button for an existing row)?
I figured out how to get a row to be pre-populated for you, although I'm not sure if this is necessarily the best way to do it, but it does accomplish the job - I'm more familiar with AngularJs, not so much with Kendo UI.
The only place that the Kendo API allows you to change/set the new item that you are adding is in the edit event but I couldn't see a way to send your own object along to the event when you call addRow so you need to have a reference to a shared object in your controller with I called itemForAdd. Before calling addRow() in your controller, you need to set the itemForAdd object with the actual object that you want to pre-populate the form with.
var itemForAdd = {};
$scope.$on('AddRow', function(event, item) {
// save reference to item to use for pre-population
itemForAdd = item;
$scope.ticketLineGrid.addRow();
});
Now in the edit event that the Kendo API sends out, you can populate the items from your selected item in the model item. It's not really required, but I also like to clear out objects that I use like this so in the save and cancel events, I clear out the shared itemForAdd object.
edit: function (e) {
if (e.model.isNew()) {
e.model.set("itemNo", itemForAdd.itemNo);
e.model.set("itemDescr", itemForAdd.itemDescr);
}
var popupWindow = e.container.getKendoWindow();
e.container.find(".k-edit-form-container").width("auto");
popupWindow.setOptions({
width: 640
});
},
save: function(e) {
if (e.model.isNew()) {
// clear out the shared object
itemForAdd = {};
}
},
cancel: function(e) {
if (e.model.isNew()) {
// clear out the shared object
itemForAdd = {};
}
}
With the previous changes, the functionality that you want is mostly working but the data in the table in the edit popup doesn't show the updated values. This is because the Kendo data bindings apparently didn't know they had to update. I couldn't figure out how to make that work, so I just used the AngularJs style bindings for that table (where you had +=itemNo=+), so that the values in the table would update based on the changes in the model object:
<tbody>
<tr>
<td>{{dataItem.itemNo}}</td>
<td>{{dataItem.itemDescr}}</td>
<td>{{dataItem.cat}}</td>
<td>{{dataItem.mfg}}</td>
<td>{{dataItem.mfgPartNo}}</td>
</tr>
</tbody>
But there was one more issue at this point, only the itemNo was being updated, not the itemDescr and that was because itemDescr was set as editable: false in your grid configuration, so I had to changed it to editable: true
fields: {
id: { type: "string", editable: false },
itemDescr: { type: "string", editable: true },
...
},
And finally, here is an updated plunker with my changes: http://plnkr.co/edit/rWavvMh4dRFAsJjuygQX?p=preview

Resources