How to display observable in html binding with empty container knockoutjs - javascript

I have an array like this which i am trying to bind with a select.
var arr = [{
"Id": 1,
"Rate": 5,
"Price": 200,
"Name": "History",
"template": "<option id='1'>History</option>"
}, {
"Id": 2,
"Rate": 5,
"Price": 150,
"Name": "Geographic",
"template": "<option id='2'>Geographic</option>"
}, {
"Id": 3,
"Rate": 1,
"Price": 75,
"Name": "Maths",
"template": "<option id='3'>Maths</option>"
}, {
"Id": 4,
"Rate": 2,
"Price": 50,
"Name": "Statistics",
"template": "<option id='4'>Statistics</option>"
}, {
"Id": 5,
"Rate": 3,
"Price": 120,
"Name": "Drawing",
"template": "<option id='5'>Drawing</option>"
}]
As you can see there is template which contains a string for option. This i have created with some function. Now i want to bind this array to select.
self.Result = ko.observableArray(arr)
View
<select data-bind="foreach:Result">
<!-- ko html:$data.template -->
<!-- /ko -->
</select>
And now it produces an error. html binding can not be used with virtual elements.
Moreover if i try this
ko.virtualElements.allowedBindings.html = true;
It does not solve the problem as i assume it is only for custom bindings.
Is there any solution for this? What should i do if i need to procees with this

On solution would be to directly use the html binding on the select element and manually concatenate your template to one string:
<select data-bind="html: Result.map(function(i) { return i.template }).join('\n')">
</select>
Demo JSFiddle.
However if you could then you should change your design and not send back the template html but build it on the client:
<select data-bind="foreach:Result">
<option data-bind="attr: {id: Id}, text: Name"></option>
</select>
Demo JSFiddle.

Thanks for the solution #nemesv provided i came to this point
self.LoadTemplate = function(){
return self.Result().map(function(i) { return i.template }).join('\n')
}
And
<select data-bind="html:LoadTemplate()"></select>
Still i would like html binding to support virtual elements.

Related

Analysis Paralysis in looping over JSON data to populate select box with optgroup sections

I have Analysis Paralysis and need some input. I can modify the SQL query, the JavaScript, AND/or the CFML controller (all code has been posted below).
All I'm looking to do is to populate a select box with options and optgroups. The optgroup is what is tripping me up here.
The sql is pretty basic and looks like this:
SELECT
g.groupID,
g.groupLabel,
u.unitLabel,
u.unitID
FROM
group g
LEFT JOIN unit u ON g.groupID = u.groupID
And the CFML loop(s) is as follows (this is also where I believe the adjustment should be made with some logic such as if thisGroupLabel matches the preGroupLabel, stay within loop and keep adding unitLabel and unitIDs) but is there a more efficient way?:
local.data.unitLabels = [];
for(local.row in local.__unitLabels){
local.unit = {};
local.unit.groupLabel = local.row.groupLabel;
local.unit.unitLabel = local.row.unitLabel;
local.unit.unitID = local.row.unitID;
// loop over the array so that we can identify which one needs to be preselected
for(local.dataValue in local.data.unitDetails){
if (local.unit.unitID eq local.dataValue.unitID) {
local.unit.selected = 'selected';
} else {
local.unit.selected = '';
}
}
arrayAppend(local.data.unitLabels, local.unit);
}
The JSON data looks like this but I have access to the query so I can reformat it if needed:
{
"data": {
"selectDataOptions": [{
"groupLabel": "COMPLETION",
"selected": "",
"unitID": 1,
"unitLabel": "Completion"
}, {
"groupLabel": "DISTANCE",
"selected": "",
"unitID": 2,
"unitLabel": "Meters"
}, {
"groupLabel": "DISTANCE",
"selected": "",
"unitID": 3,
"unitLabel": "Miles"
}, {
"groupLabel": "DISTANCE",
"selected": "",
"unitID": 4,
"unitLabel": "Yards"
}, {
"groupLabel": "TIME",
"selected": "",
"unitID": 5,
"unitLabel": "Hours"
}, {
"groupLabel": "TIME",
"selected": "",
"unitID": 5,
"unitLabel": "minutes"
}, {
"groupLabel": "TIME",
"selected": "",
"unitID": 5,
"unitLabel": "Seconds"
}]
}
}
As it stands, my select box looks like this (roughly):
<select>
<optgroup>COMPLETION</optgroup>
<option>Complettion</option>
<optgroup>DISTANCE</OPTGROUP>
<option>Meters</option>
<optgroup>DISTANCE</optgroup>
<option>Miles</option>
<optgtroup>DISTANCE</optgroup>
<option>Yards</option>
<optgtroup>TIME</optgroup>
<option>Hours</option>
<optgtroup>TIME</optgroup>
<option>Minutes</option>
</select>
Notice that the optgroup Distance and TIME are repeated. The desired output would look like this:
<select>
<optgroup>COMPLETION</optgroup>
<option>Complettion</option>
<optgroup>DISTANCE</OPTGROUP>
<option>Meters</option>
<option>Miles</option>
<option>Yards</option>
<optgroup>TIME</optgroup>
<option>Hours</option>
<option>Mintues</option>
</select>
Is the issue how to construct a JSON string that Select2 can understand? I'd suggest creating a nested array of children for each GroupLabel, as described in the documentation under Grouped Data.
CF11+ and Lucee 4.5+ support cfloop "group", which would make things a lot easier. Just cfloop through the query and group by "groupLabel". (NB: Don't forget to modify the SQL query and ORDER BY g.groupLabel so the grouping works as expected.)
TryCF.com Example
Code:
data= [];
cfloop(query="qDemo", group="groupLabel") {
children = [];
cfloop() {
arrayAppend(children, {"id": qDemo.unitID, "text": qDemo.unitLabel});
}
arrayAppend(data, {"text" : qDemo.GroupLabel, "children" : children });
}
writeDump(serializeJSON(data));
Result:
[
{
"text": "COMPLETION",
"children": [
{
"text": "Completion",
"id": 1
}
]
},
{
"text": "DISTANCE",
"children": [
{
"text": "Meters",
"id": 2
},
{
"text": "Miles",
"id": 3
},
{
"text": "Yards",
"id": 4
}
]
},
{
"text": "TIME",
"children": [
{
"text": "Hours",
"id": 5
},
{
"text": "minutes",
"id": 5
},
{
"text": "Seconds",
"id": 5
}
]
}
]

Generate Anchor Links based on ID (JSON, jQuery)

I am trying to populate anchor links based on the ID in JSON data.
So far I have this JSON data.
{
"_meta": {
"status": "ok",
"api_version": 1,
"per_page": 20,
"current_page": 1,
"total_pages": 5,
"links": {
"next": "?page=2",
"previous": null
}
},
"data": [
{
"id": 1,
"name": "Andrew"
},
{
"id": 2,
"name": "Josh"
},
{
"id": 3,
"name": "John"
}
]
}
Here is my HTML code.
<div id = "links"></div>
Here is my jQuery code for fetching ids and displaying them on Anchor.
$.each(json.data, function(entryIndex, entry){
$("a.names").attr("href", "details?=" + entry.id);
$("#links").append('<a class = "names">View</a>');
console.log(entry.id);
});
What I'm trying to achieve here is to generate anchor links in , which would look like this on HTML.
View
View
View
Instead, the results were,
View
View
View
So, I debugged using console.log, which returned
1
2
3
How can I achieve this from getting ID of JSON and assign in Anchor Link?
This is not the way you do:
$("a.names").attr("href", "details?=" + entry.id);
So instead what you need to do is:
$("a.names").last().attr("href", "details?=" + entry.id);
The above will fetch the last one inserted. This is a dirty working fix.
In reality, you must do this way:
$.each(json.data, function(entryIndex, entry){
$("#links").append('<a class="names" href="details?=" + entry.id>View</a>');
console.log(entry.id);
});
Snippet
json = {
"_meta": {
"status": "ok",
"api_version": 1,
"per_page": 20,
"current_page": 1,
"total_pages": 5,
"links": {
"next": "?page=2",
"previous": null
}
},
"data": [
{
"id": 1,
"name": "Andrew"
},
{
"id": 2,
"name": "Josh"
},
{
"id": 3,
"name": "John"
}
]
}
$.each(json.data, function(entryIndex, entry){
$("#links").append('<a class="names" href="details?=" + entry.id>View</a>');
console.log(entry.id);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="links"></div>

Pre populating the dropdown value inside ng-repeat in Angular JS

I have an array of objects. I have three properties attorneyId, attorneyName and attorneyList. The attorneyList array should be a drop down. If attorneyId = attorneyName = null then
dropdown will only show Choose attorney. If attorneyId != null and attorneyName != null, the dropdown should display what ever the value is present in attorneyId and attorneyName.
For this i am preparing an object selectedAttorney = {id:list.attorneyId, name:list.attorneyName} and passing this to ng-model of select. When i change the value in dropdown, i am getting the correct values into the selectedAttorney. But if there some value present for attorneyId and attorneyName, the dropdown is not pre populating the value.
HTML :
<div ng-repeat = 'list in List'>
<div class="width-200" ng-init="selectedAttorney = {id:list.attorneyId, name:list.attorneyName}">
{{selectedAttorney}}
<select ng-model="selectedAttorney" ng-options="attorney.name for attorney in list.attorneyList">
<option value="">-- choose attorney --</option>
</select>
</div>
</div>
JS
$scope.List = [ {
"attorneyId": "3",
"attorneyName": "Robert",
"attorneyList": [
{ "id": 1, "name": "sue" },
{ "id": 2, "name": "anthony" },
{ "id": 3, "name": "Robert" },
{ "id": 4, "name": "George" },
{ "id": 5, "name": "Bruce" }
]
},
{
"attorneyId": null,
"attorneyName": null,
"attorneyList": [
{ "id": 1, "name": "sue" },
{ "id": 2, "name": "anthony" },
{ "id": 3, "name": "Robert" },
{ "id": 4, "name": "George" },
{ "id": 5, "name": "Bruce" }
]
}
];
Here is the plunker link.
http://plnkr.co/edit/eqBkX0DNleiFmlO5sm7J?p=preview
Thanks in advance.
Though your approach is right, I prefer writing my select boxes in this manner.
<select ng-model="selectedAttorney">
<option
ng-repeat="attorney in list.attorneyList"
title="{{attorney.name}}"
ng-selected="{{attorney.id == selectedAttorney.id}}"
value="{{attorney}}">{{attorney.name}}
</option>
</select>
and here is the updated working demo : http://plnkr.co/edit/vCNOAPWlR6Mj1kFOgCKT?p=preview

AngularJS rendering takes a lot of time

I've got a model that's like this:
{
"items": [
{
"type": "A",
"id": "2",
"number": 0,
"info": "info1",
"childs": [
{
"type": "B",
"id": "21",
"number": 0,
"info": "info1",
"childs": []
}
]
},
{
"type": "A",
"id": "1",
"number": 0,
"info": "info1",
"childs": []
},
{
"type": "A",
"id": "2",
"number": 0,
"childs": [
{
"type": "B",
"id": "21",
"number": 0,
"info": "info1",
"childs": [
{
"type": "C",
"id": "211",
"number": 0,
"info": "info1",
"childs": [
{
"type": "B",
"id": "2111",
"info": "info1",
"number": 0
}
]
}
]
}
]
}
]
}
that is rendered in the page via 4 nested ng-repeat. The property number is changed via an input="number", one for each row. The info value is changed by a selectbox
The structure is like this.
<div ng-repeat="item in items">
<input type="number" ng-model="item.number" />
<select ng-model="item.info">
</select>
<div ng-repeat="child in items.child">
<input type="number" ng-model="child.number" />
<select ng-model="child.info">
</select>
<div ng-repeat="child2 in child.child">
<input type="number" ng-model="child2.number" />
<select ng-model="child2.info">
</select>
<div ng-repeat="child3 in child2.child">
<input type="number" ng-model="child3.number" />
<select ng-model="child3.info">
</select>
</div>
</div>
</div>
</div>
Well that's not exactly how's the actual webapp but it's enough for you. Anyway, if I change the value of an input="number", the rendering process takes like 1500ms to complete. The same (but the rendering is a little bit faster) happens when I change the selectbox selected option.
When I change the value I just need to update the property of the item in the model. Why does angular render all over again?
What can I do?
Keep in mind that usually the model contains around 10/15 items with 5 to 20 child each.
PS: Weird thing, when I change the input value clicking on the spinners (the default ones that appear when I use Chrome), the number is incremented or decremented by 2, and not 1.
EDIT: Still haven't solved this problem. When I have too many items to display in the screen, all the rendering process is too slow with four nested ng-repeat. Anyone has an alternative solution for display my model in the page and modify the values without losing 2s on rendering? I can't use pagination or infinite scrolling.

Angularjs trigger country state dependency

Can someone please help me make my example of Country/State drop down dependency work?
I intentionally created JSON in this way because I want the dependency to be generic, so I would be able to apply it on any drop down while using only Meta Data and not HTML.
Here's a link to see an example of the code in JSFidlle
HTML
Country:<select data-ng-model="Countries" data-ng-options="country.id as country.name for country in Countries.items">
<option value="">Please select a country</option>
</select>
State: <select data-ng-model="currentItem" data-ng-options="item.id as item.name for item in currentStates.items">
<option value="">Please select a state</option>
</select>
JavaScript Code:
function Controller($scope) {
var Countries = {
"id": "field10",
"items": [{
"id": "10",
"StateGroupID":"0",
"name": "United State"
},
{
"id": "2",
"StateGroupID":"1",
"name": "Canada"
}]
};
var States =
{ "id": "field20",
"StateGroups": [{
"items": [{ "id": "1",
"name": "Alabama"
},
{
"id": "2",
"name": "Alaska"
},
{ "id": "3",
"name": "Arizona"
},
{ "id": "4",
"name": "California"
}]},
[{ "id": "201",
"name": "Alberta"
},
{
"id": "202",
"name": "British Columbia"
},
{
"id": "303",
"name": "Manitoba"
},
{
"id": "304",
"name": "Ontario"
}]]
};
$scope.Countries = Countries;
$scope.currentStates = States.StateGroups[0];
$scope.$watch('currentStates', function(value, oldValue){
//alert(value);
//alert(JSON.stringify(value));
//$scope.currentStates = (value == "10") ? States.StateGroups[0] : States.StateGroups[1];
});
}
first, I think there is a little mistake in your JSON, you should have one "items" before the Canadian states
{"items": [{ "id": "201",
"name": "Alberta"
}, .....
After doing this, I would modify your HTML in order to have 2 different model names (the way you did, at the first click you overwrite the list of countries). Then I'll use a different syntax for the ng-repeat, in order to force the value to the StateGroupId
<select data-ng-model="selectedCountry">
<option ng-repeat='country in Countries.items' value='{{country.StateGroupID}}'>{{country.name}}</option>
</select>
Doing this allows you to create a function to get the states of the selected group ID :
$scope.getStates=function(){
console.log($scope.selectedCountry)
return $scope.backupStates.StateGroups[$scope.selectedCountry].items;
}
Then you can use this function to display them using ng-repeat
<select data-ng-model="selectedState" >
<option value="">Please select a state</option>
<option ng-repeat='state in getStates()'>{{state.name}}</option>
</select>
I modified your fiddle here : http://jsfiddle.net/DotDotDot/TsxTU/14/ , I hope this is the kind of behavior you wanted :)
I suggest you a bit of refactoring to your data model - it seems tangled. Let's store counties and states in two arrays:
$scope.countries = [{
"name": "USA",
"id": 1
},{
"name": "Canada",
"id": 2
}];
$scope.states = [{
"name": "Alabama",
"id": 1,
"countryId": 1
}, {
"name": "Alaska",
"id": 2,
"countryId": 1
}, {
"name": "Arizona",
"id": 3,
"countryId": 1
}, {
"name": "Alberta",
"id": 4,
"countryId": 2
}, {
"name": "British columbia",
"id": 5,
"countryId": 2
}];
Having this, we can write selects for data:
<select data-ng-model="country" data-ng-options="country.name for country in countries" data-ng-change="updateCountry()">
<option value="">Select country</option>
</select>
<select data-ng-model="state" data-ng-options="state.name for state in availableStates">
<option value="">Select state</option>
</select>
It's a pity we cannot use if expressions in selectors - if we can, we do not need a single line of JS! But we need:
$scope.updateCountry = function(){
$scope.availableStates = [];
angular.forEach($scope.states, function(value){
if(value.countryId == $scope.country.id){
$scope.availableStates.push(value);
}
});
}
And that's all. Here is a working plunk for you.
The easiest way to fix is to refer the currentCountry in the 2nd select and no need to use the $watch to achieve your requirement.
<select data-ng-model="currentCountry" data-ng-options="country.name for country in Countries.items">
<select data-ng-model="currentItem" data-ng-options="item.id as item.name for item in States.StateGroups[currentCountry.StateGroupID].items">
Demo
angular-country-picker
bower install angular-country-picker (or)
npm install angular-country-picker
<script src="bower_components/angular-country-picker/country-picker.js"></script>
<script src="node_modules/angular-country-picker/country-picker.js"></script>
angular.module('webApp', ['puigcerber.countryPicker']);
<select ng-model="selectedCountry" pvp-country-picker="name"></select>
angular.module('myApp', ['puigcerber.countryPicker'])
.config(function(pvpCountriesProvider) {
pvpCountriesProvider.setCountries([
{ name: 'Abkhazia', alpha2: 'AB'},
{ name: 'Kosovo', alpha2: 'XK'},
{ name: 'Nagorno-Karabakh', alpha2: 'NK'},
{ name: 'Northern Cyprus', alpha2: 'KK'},
{ name: 'Somaliland', alpha2: 'JS'},
{ name: 'South Ossetia', alpha2: 'XI'},
{ name: 'Transnistria', alpha2: 'PF'}
]);
});

Categories

Resources