I am creating a dynamic report builder which can have multiple rows. These rows are created dynamically based on the field selected. I am appending the dynamic HTML through my js controller but ng-model is not binded to the DOM. I tried adding $compile(element)($scope) but it didnt work. Please find the screenshot shared below.
HTML
<div class="page-content-wrap">
<form name="reportform" class="reportBuilder" ng-submit="getReport(reportform,report)">
<div class="row">
<div class="form-group col-md-4">
<select class="form-control" name="module" id="module"
ng-model="modules"
ng-options="module as module.formname for module in allModules track by module.id"
ng-change="getFields()">
<option value="" class="">Select Module</option>
</select>
</div>
</div>
<div class="dynamic_block">
<div class="row">
<div class="form-group">
<div class="col-md-4">
<select class="form-control field_type"
ng-model="fields[row_count]"
ng-options="moduleField.name as (moduleField.label) for moduleField in moduleFields">
<option value="" class="">Select Fields</option>
</select>
</div>
{{options}}
<div class="col-md-7 field_to_be_added">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<span ng-click="cloneRow()"><i class="fa fa-plus-circle"></i></span>
</div>
</div>
<div class="row">
<div class="col-md-6">
<button class="btn btn-primary" type="submit">Generate Report</button>
</div>
</div>
</form>
</div>
JavaScript
$scope.cloneRow = function() {
$scope.row_count++;
$scope.model_name = 'field_'+$scope.row_count;
var new_html = $('#dynamic_block_fixed').clone();
$('.dynamic_block:last').after(new_html);
$compile(new_html)($scope);
$('.minus-icon').unbind().bind('click',function(item){
$(item.target).closest('div.dynamic_block').remove();
});
$('.field_type').unbind().bind('change',function(item){
var item_id = $(this).val().split(':');
for(fields in $scope.moduleFields){
if(typeof $scope.moduleFields[fields] != 'undefined') {
if($scope.moduleFields[fields]['name'] == item_id[1]){
createFieldOptions($scope.moduleFields[fields],item.target.parentElement);
}
}
}
});
}
function createFieldOptions(field, target) {
for (f in field) {
var name = field['name'];
if (field[f] == 'text') {
var element = `<input class="form-control" dynamic type="text" ng-model="report.${name}"/>`;
} else if (field[f] == 'textarea') {
var element = `<textarea class="form-control" ng-model="report.${name}"></textarea>`;
} else if (field[f] == 'select') {
var options = [];
for (key in $scope.moduleFields) {
var objectValues = $scope.moduleFields[key];
if (objectValues['type'] == 'select' && objectValues['name'] == name) {
options = objectValues['values'];
}
}
var element = `<select class="form-control" ng-model="report.${name}"><option>Select</option>`;
options.forEach(function(object) {
element += `<option value="${object.value}">${object.label}</option>`;
});
element += "</select>";
} else if (field[f] == 'checkbox-group') {
var checkoptions = [];
for (key in $scope.moduleFields) {
var objectValues = $scope.moduleFields[key];
if (objectValues['type'] == 'checkbox-group' && objectValues['name'] == name) {
checkoptions = objectValues['values'];
}
}
var element = "<span>";
checkoptions.forEach(function(object) {
element += `<label class="check"><input type="checkbox" name="${name}" ng-model="report.${name}" value="${object.value}"/>${object.label}</label>`;
});
element += "</span>";
} else if (field[f] == 'date') {
element = `<input type="date" name="${name}" ng-model="report.${name}"/>`;
} else if (field[f] == 'radio-group') {
var radiooptions = [];
for (key in $scope.moduleFields) {
var objectValues = $scope.moduleFields[key];
if (objectValues['type'] == 'radio-group' && objectValues['name'] == name) {
radiooptions = objectValues['values'];
}
}
var element = "<span>";
radiooptions.forEach(function(object) {
element += `<label class="check"><input type="radio" name="${name}" ng-model="report.${name}" value="${object.value}"/>${object.label}</label>`;
});
element += "</span>";
}
}
angular.element(target).next('div.col-md-7').html(element);
//$(target).next('div.col-md-7').html(element);
$compile(element)($scope);
$scope.$apply();
}
Open to alternative approach if any.
It sounds like you're appending the dynamic HTML in the JQuery style, with parent.append(newHTML) kinda thing; That's not great practice in AngularJS, and also when you have to use $compile, you know something's not quite right.
I would recommend structuring your code in directives and showing and hiding each directive via ng-if.
If you give some HTML examples, I could go more in depth
Related
I am having issue with ng-repeat , its replacing all values with latest one.
E.g. I am adding a value to textbox then adding that value in ng-repeat div but its replacing all values with last value entered.
Here is Jsfiddle
https://jsfiddle.net/mahajan344/9bz4Lwxa/656/
This is happening because you have only one statusObj and you are modifying it every time someone clicks the Add New Status button. Delete the statusObj you have now, and have the AddNewStatus method create a new one each time:
var xyzApi = xyzApi || {
sayHello: function() {
return "hey there\n";
}
};
angular.module('demoApp', [])
.controller('MainController', MainController)
.provider('xyzApi', function XyzApiProvider() {
this.$get = function() {
var xyzApiFactory = {
otherFunction: function() {
//$log.log('other function called');
return 'other function \n';
}
};
//console.log(xyzApiFactory, xyzApi);
angular.merge(xyzApiFactory, xyzApi);
return xyzApiFactory;
};
});
function MainController(xyzApi) {
var vm = this;
vm.test = '';
vm.listOfStatus = [];
vm.showStatusError = false;
vm.statusText = "";
vm.sayHello = function() {
vm.test += xyzApi.sayHello() + xyzApi.otherFunction();
}
vm.AddNewStatus = function(statusText) {
if (statusText.length < 1) {
vm.showStatusError = true;
return;
} else {
vm.showStatusError = false;
}
var statusObj = {
StatusComment: statusText,
scId: 0,
scTimeStamp: new Date(),
JobNum: 0,
IsNew: 0,
};
vm.listOfStatus.push(statusObj);
vm.statusText = "";
};
vm.RemoveStatus = function(index) {
vm.listOfStatus.splice(index, 1);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-rc.0/angular.js"></script>
<div ng-app="demoApp" ng-controller="MainController as mainCtrl">
<pre>{{mainCtrl.test}}</pre>
<button ng-click="mainCtrl.sayHello()">
say hello!!
</button>
<div id="DivStatus">
<div class="form-group">
Status
<div class="col-md-3 col-sm-3 col-xs-12">
<input type="text" ng-model="mainCtrl.statusText" id="txtStatus" class="form-control col-md-7 col-xs-12">
<div class="text-danger error-message" id="txtStatusError" ng-show="showStatusError">Please enter new status</div>
</div>
<div class="col-md-3 col-md-3x col-sm-3 col-xs-12">
<input type="button" class="btn" ng-click="mainCtrl.AddNewStatus(mainCtrl.statusText)" value="Add New Status" />
</div>
</div>
<div class="form-group" ng-repeat="statusObj in mainCtrl.listOfStatus track by $index">
<div class="col-md-3 col-sm-3 col-xs-12">
<input type="text" value="{{statusObj.StatusComment}}" ng-disabled="true" class="form-control col-md-7 col-xs-12">
</div>
<span class="remove-record" ng-click="mainCtrl.RemoveStatus($index)" style="cursor:pointer"><i class="fa fa-times"></i></span>
</div>
</div>
</div>
I have a text box that show when I click on checkbox and I need to make an event on it that can make me bind it to object from DB
HTML:
<div ng-repeat="feture in featureEnumArray">
<input id="txtchkFet" type="checkbox" ng-model="feture.IsChecked" />
{{feture.DisplayText}}
<div ng-show="feture.IsChecked" >
<input class="form-control" type="text" ng-model="feture.Value" ng-change="getFeatureID(feture)" id="txtValue" placeholder="Type Feature Value" />
</div>
<div class="label label-danger" ng-show="feture.IsChecked && !feture.Value">
Type Value
</div>
</div>
And in Angular controller I did Like this :
$scope.getFeatureID = function (feture) {
if (feture.IsChecked) {
$scope.Planfeature = angular.copy(feture);
$scope.PlanFeatureTemp.FeatureID = $scope.Planfeature.ID;
$scope.PlanFeatureTemp.Value = $scope.Planfeature.Value;
$scope.Planfeature = [];
}
else {
var index = $scope.JAdminPlan.PlanFeatureValues.indexOf(feture);
$scope.JAdminPlan.PlanFeatureValues.splice(index, 1);
}
if (!$scope.$$phase) $scope.$apply();
};
I am using datatable with yii1. I have following code in my view file:
<div class="form-group col-xs-12 col-md-4">
<label class="col-sm-6 control-label" for="formGroupInputSmall">Member From</label>
<div class="col-sm-6">
<input type='date' class="form-control" value='' id='fini' data-column-index='8'>
</div>
</div>
<div class="form-group col-xs-12 col-md-4">
<label class="col-sm-6 control-label" for="formGroupInputSmall">Member to</label>
<div class="col-sm-6">
<input type='date' class="form-control" value='' id='ffin' data-column-index='9'>
</div>
</div>
In js file I have following code:
$(document).ready(function() {
var table = $('#table_id').DataTable();
/* Add event listeners to the two range filtering inputs */
$('#fini').keyup( function() { table.draw(); } );
$('#ffin').keyup( function() { table.draw(); } );
} );
$.fn.dataTableExt.afnFiltering.push(
function( oSettings, aData, iDataIndex ) {
var iFini = document.getElementById('fini').value;
var iFfin = document.getElementById('ffin').value;
var iStartDateCol = 8;
var iEndDateCol = 9;
iFini=iFini.substring(6,10) + iFini.substring(3,5)+ iFini.substring(0,2);
iFfin=iFfin.substring(6,10) + iFfin.substring(3,5)+ iFfin.substring(0,2);
var datofini=aData[iStartDateCol].substring(6,10) + aData[iStartDateCol].substring(3,5)+ aData[iStartDateCol].substring(0,2);
var datoffin=aData[iEndDateCol].substring(6,10) + aData[iEndDateCol].substring(3,5)+ aData[iEndDateCol].substring(0,2);
if ( iFini === "" && iFfin === "" )
{
return true;
}
else if ( iFini <= datofini && iFfin === "")
{
return true;
}
else if ( iFfin >= datoffin && iFini === "")
{
return true;
}
else if (iFini <= datofini && iFfin >= datoffin)
{
return true;
}
return false;
}
);
When I input date, datatable returns no results. If I change type of <input/> to text, It will search for exact date (not specific date range).
I am trying to get the array id of selected checkbox but unable to get the array. I need the array in format of ["302740", "42006", "331497"]
here it is my app.js
var app = angular.module('app', []);
function Ctrl($scope) {
$scope.categories = [ {"tid":"302740","name":"2 BE 3"},{"tid":"42006","name":"A Line"},{"tid":"331497","name":"Activa"} ];
$scope.brandlist = [];
$scope.get = function (val) {
console.log(val);
var idx = $scope.brandlist.indexOf(val);
if (idx > -1)
$scope.brandlist.splice(idx, 1);
else
$scope.brandlist.push(val);
console.log($scope.brandlist);
// should come like selected array ["302740", "42006", "331497"]
}
}
and here it is the html
<div ng-controller="Ctrl">
<span ng-repeat="(key,val) in categories">
<label class="checkbox" for="{{key.tid}}">
<input type="checkbox" ng-change="get(val.tid)" ng-model="brandlist[val.tid]" ng-checked="brandlist.indexOf(val.tid) > -1" name="group" id="{{val.tid}}" />
{{val.name}}
</label>
</span>
</div>
plunker
Consider trying this
$scope.categories = [ {"tid":"302740","name":"2 BE 3"},{"tid":"42006","name":"A Line"},{"tid":"331497","name":"Activa"} ];
$scope.selecetedCategories = function toggleSelection(category){
var idx = $scope.selectedCategories.indexOf(category);
if(idx > -1){
$scope.selecetedCategories.splice(idx,1);
}
else{
$scope.selecetedCategories.push(category);
}
};
You can print the selected categories like this
console.log($scope.selecetedCategorie);
And your html:
<input type="checkbox" name="selectedCategories"
value="{{category}}" ng-checked="selectedCategories.indexOf(category) >
-1" ng-click="toggleSelection(category)"/>
If you're not worried about manipulating the source array, you can use that to maintain the state.
HTML
<div ng-controller="Ctrl">
<span ng-repeat="(key,val) in categories">
<label class="checkbox" for="{{key.tid}}">
<input type="checkbox" ng-model="val.checked" name="group" id="{{val.tid}}" />
{{val.name}}
</label>
</span>
</div>
JS:
$scope.categories = [ {"tid":"302740","name":"2 BE 3"},{"tid":"42006","name":"A Line"},{"tid":"331497","name":"Activa"} ];
// get the selected categories
var selectedCategories = $scope.categories.filter(function(itm) {
return itm.checked;
});
I am using the following versions:
Ionic, v1.0.0-beta.14
AngularJS v1.3.6
Route configuration:
myApp.config(function ($stateProvider) {
$stateProvider
.state('manager_add', {
url: '/managers/add',
templateUrl: 'app/components/mdl.role_members/views/add.html',
controller: 'ManagerAddController'
});
});
Controller configuration:
myApp.controller('ManagerAddController', function ($scope, $state, $ionicLoading, $filter, ContactService, ManagersService, RoleRequest, RoleRequestsSentService, ToastrService) {
$scope.role_request = RoleRequest.new();
$scope.showContactSearch = false;
$scope.managers = ManagersService.collection();
$scope.$watchCollection("managers", function( newManagers, oldManagers ) {
if(newManagers === oldManagers){ return; }
$scope.managers = newManagers;
$scope.contactsToBeInvited = getNotInvitedContacts();
});
$scope.contacts = ContactService.collection();
$scope.$watchCollection("contacts", function( newContacts, oldContacts ) {
if(newContacts === oldContacts){ return; }
$scope.contacts = newContacts;
$scope.contactsToBeInvited = getNotInvitedContacts();
});
$scope.contactsToBeInvited = getNotInvitedContacts();
function getNotInvitedContacts() {
var notinvited = [];
angular.forEach($scope.contacts, function(contact) {
if(angular.isObject($scope.managers)) {
var results = $filter('filter')($scope.managers, {member_id: Number(contact.contact_id)}, true);
if (results.length == 0) {
this.push(contact);
}
} else {
this.push(contact);
}
}, notinvited);
return notinvited;
}
$scope.search_contact = "";
$scope.search = function(contact) {
if($scope.search_contact === "" || $scope.search_contact.length === 0) {
return true;
}
$scope.showContactSearch = true;
var found = false;
if(contact.display_name) {
found = (contact.display_name.toLowerCase().indexOf($scope.search_contact.toLowerCase()) > -1);
if(found) { return found; }
}
if(contact.contact.getFullName()) {
found = (contact.contact.getFullName().toLowerCase().indexOf($scope.search_contact.toLowerCase()) > -1);
if(found) { return found; }
}
if(contact.contact.email) {
found = (contact.contact.email.toLowerCase().indexOf($scope.search_contact.toLowerCase()) > -1);
if(found) { return found; }
}
return found;
}
$scope.selectContact = function(contact) {
$scope.search_contact = contact.contact.getFullName();
// TODO: Make dynamic role
$scope.role_request.role_id = 4;
$scope.role_request.email = contact.contact.email;
};
$scope.addRoleMember = function(valid) {
if($scope.role_request.email === "") { return; }
if(!valid) { return; }
$ionicLoading.show({
template: 'Please wait...'
});
RoleRequestsSentService.add($scope.role_request).then(function(roleJsonResponse){
ToastrService.toastrSuccess('Request send', 'We have send an invite to '+ $scope.search_contact +'.');
$ionicLoading.hide();
$state.go('managers');
});
}
});
View configuration:
<ion-view view-title="ManagerAdd" >
<ion-content class="has-header scroll="true">
<div class="content">
<div class="list">
<div class="item item-border">
<p>Some text</p>
</div>
</div>
<form name="managerForm">
<div class="list">
<div class="item item-divider">
Some text
</div>
<div class="item item-border">
<form name="fillForm">
<div class="form-group">
<label class="item item-input item-stacked-label item-textarea">
<span class="input-label border-none">Personal message: <span class="text-red required">*</span></span>
<textarea name="message" ng-model="role_member.message" required></textarea>
</label>
<p ng-show="managerForm.message.$dirty && managerForm.message.$error.required"
class="error-message">Message required!</p>
</div>
<div class="form-group">
<label class="item item-input">
<span class="input-label">Search on name <span class="text-red required">*</span></span>
<input type="text" name="search_contact" ng-model="$parent.search_contact">
</label>
<div class="searchResultBox" ng-show="showContactSearch">
<ion-scroll direction="y" class="scrollArea">
<div class="list">
<a class="item item-border item-avatar pointer" ng-repeat="contact in $parent.filteredContacts = (contactsToBeInvited | filter:search:false)" ng-click="$parent.selectContact(contact)">
<img src="{{ contact.getImage('thumbnail') }}">
<h2>{{contact.getIconName()}}</h2>
<p>City: {{contact.contact.city}}</p>
</a>
</div>
</ion-scroll>
<div class="notFound pointer" ng-hide="filteredContacts.length">
<h3>Nobody found</h3>
<p>You can only search through existing contacts</p>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="form-actions">
<button type="submit" class="button button-block regie-button" ng-click="addRoleMember(registerForm.$valid)">
Sent request
</button>
</div>
</form>
<p class="text-red" style="text-align:center; font-size:14px; font-weight: 400;">* required</p>
</div>
</ion-content>
</ion-view>
As you can see in the view I need to use $parent to the following fields to get it to work:
ng-model="$parent.search_contact"
ng-repeat="contact in $parent.filteredContacts = (contactsToBeInvited | filter:search:false)"
ng-click="$parent.selectContact(contact)"
I really don't understand why this is necessary because the complete view is using the same controller. Does anyone have an idea?
This is a very typical angular scoping issue. Ion-view creates a new child scope and uses prototypical inheritance: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Three are several ways to solve:
always use a dot: https://egghead.io/lessons/angularjs-the-dot
use the 'controller as' syntax: http://www.johnpapa.net/angularjss-controller-as-and-the-vm-variable/
The problem is the inheritance. Between your controller's scope and those fields, there are several new scopes (ion-content, ion-scroll and probably others).
So for example the ng-model="search_content". When you write in there, it is going to create a search_content variable inside the ion-content scope (or an intermediary scope if there is any that I didn't see). Since search_content is being created inside ion-content, your controller won't see it.
If you do $parent.search_content it will create it in the parent content (AKA the controller's scope).
You shouldn't do that, what $parent is for you today, tomorrow it can point to anything else (because you could add a new scope in between without knowing it so $parent will then point to the ion-content.
So instead of doing that, you need to use objects and no primitives, for example:
ng-model="form.search_contact"
Thank to that, it will look for the form object through the prototype chain until it finds it on the controller's scope and use it (just what you need).
Read this which is hundred times better than my explanation.