Nested Directive Not Updating View(DOM) Element in AngularJs - html

I've made a nested directive, and tried to call it from a html view,but not updating the view element in html.
I can get the updated value from the calling controller but can't see the updated effect in view level.
Here is the Js
// Code goes here
"use strict";
var myApp=angular.module("myApp",[]);
myApp.directive("selectDirective", [function () {
return {
restrict: "E",
template: '<select class="form-control input-sm dropdown" data-ng-model="model.args.selectedItem" data-ng-options="item[model.args.displayField] for item in model.args.source" data-ng-change="model.itemChange(model.args.selectedItem)"><option value="">Select Any Item</option></select>',
scope: {
},
bindToController: { args: "=" },
link: function (scope, element, attrs) {
var self = scope.model || {};
var initializeControl = function () {
if (self.args == undefined) {
self.args = {};
}
if (self.args.method == undefined) {
self.args.method = {};
}
if (self.args.isDisabled == undefined) {
self.args.isDisabled = false;
}
if (self.args.displayField == undefined) {
self.args.displayField = '';
//alert('Display Field is blank for dropdown control.')
}
if (self.args.valueField == undefined) {
self.args.valueField = '';
//alert('Value Field is blank for dropdown control.')
}
if (self.args.source == undefined) {
self.args.source = {};
}
if (self.args.hide == undefined) {
self.args.hide = false;
}
}
var assignMethod = function () {
self.args.method =
{
setEnable: function (args) {
self.args.isDisabled = !args;
},
setVisible: function (args) {
self.args.hide = !args;
},
getText: function () {
return self.args.selectedText;
},
getValue: function () {
return self.args.selectedValue;
},
setItem: function (item) {
if (item != undefined) {
var index = self.args.source.indexOf(item);
self.args.selectedText = item[self.args.displayField];
self.args.selectedValue = item[self.args.valueField];
self.args.selectedItem = item;
self.args.selectedIndex = index;
}
}
}
}
self.itemChange = function (item) {
if (item != undefined) {
var index = self.args.source.indexOf(item);
self.args.selectedText = item[self.args.displayField];
self.args.selectedValue = item[self.args.valueField];
self.args.selectedItem = item;
self.args.selectedIndex = index;
}
}
initializeControl();
assignMethod();
},
controller: function () { },
controllerAs: 'model'
}
}]);
myApp.directive("stateDirective", [function () {
return {
restrict: "E",
template: '<select-directive args="model.args"></select-directive>',
scope: {},
bindToController: { args: "=" },
link: function (scope, element, attrs) {
var self = scope.model || {};
var initializeControl = function () {
if (self.args == undefined) {
self.args = {};
}
var sourceList = [{ id: 1, name: "AA", value: "AA" },
{ id: 2, name: "AB", value: "AB" },
{ id: 3, name: "ABD", value: "ABD" },
{ id: 4, name: "ABE", value: "ABE" },
{ id: 5, name: "ACT", value: "ACT" },
{ id: 6, name: "AE", value: "AE" }];
self.args.source = sourceList;
self.args.displayField = 'name';
self.args.valueField = 'value';
}();
},
controller: function () {
},
controllerAs: 'model'
}
}]);
myApp.controller("homeController", ['$interval', function ($interval) {
var self = this;
var initializeControl = function () {
self.state1 = {};
self.state2 = {};
self.ClickMe = function () {
debugger;
aler(1);
self.state2.method.setItem(self.state1.selectedItem);
}
self.ClickMe2 = function () {
debugger;
aler(1);
var x1 = self.state1;
var x2 = self.state2;
}
};
$interval(function () {
}, 500);
initializeControl();
}]);
Here is the Html:
<div class="cold-md-12" ng-controller="homeController as model">
<h1>Home Page</h1>
<state-directive args="model.state1"></state-directive>
<br />
<input type="button" value="ClickMe" data-ng-click="model.ClickMe()" />
<state-directive args="model.state2"></state-directive><br />
<input type="button" value="Submit" data-ng-click="model.ClickMe2()" />
</div>
Fiddle Here.
N.B: i tried using $watch and $timeout in directive and controller also but didn't work.

In the event that scope.model is not initialized, you're assigning {} to self in your linking function. But then you never actually place it on the controller. If scope.model exists, you're fine, because you're then binding everything to the controller, which is bound to scope, but if it's not stuff goes wrong. Which I'm guessing is what's happening.
Although, if I may be so bold, I'd say that naming your controller model is very misleading. Because in the development world models and controllers are two very different things.
Cheers,
Aaron

Just added track by in ng-option.
I just change the data-ng-options value
from
item[model.args.displayField] for item in model.args.source
to
item[model.args.displayField] for item in model.args.source track by item[model.args.valueField]
and it's working perfectly.

Related

how to create a custom autocomplete component in vue js?

Currently i am using buefy autocomplete.But there are a few issues with it.
DepartmentDetail.vue
<template slot-scope="props">
<div class="container is-fluid">
<b-loading :is-full-page="true" :active.sync="this.isLoading"></b-loading>
<b-field label="Business Unit">
<b-autocomplete
:data="dataBusinessUnit"
placeholder="select a business unit..."
field="businessUnit"
:loading="isFetching"
:value="this.objectData.businessUnit"
#typing="getAsyncDataBusinessUnit"
#select="(option) => {updateValue(option.id,'businessUnit')}"
>
<template slot-scope="props">
<div class="container">
<p>
<b>ID:</b>
{{props.option.id}}
</p>
<p>
<b>Description:</b>
{{props.option.description}}
</p>
</div>
</template>
<template slot="empty">No results found</template>
</b-autocomplete>
</b-field>
</div>
</template>
Function that fetches the results based on user input-
getAsyncDataBusinessUnit: debounce(function(name) {
// console.log('getAsyncDataBusinessUnit you typed'+name);
if (!name.length) {
this.dataBusinessUnit = [];
return;
}
this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/businessunit/${name}`)
.then(response => {
this.dataBusinessUnit = [];
response.forEach(item => {
this.dataBusinessUnit.push(item);
});
})
.catch(error => {
this.dataBusinessUnit = [];
throw error;
})
.finally(() => {
this.isFetching = false;
});
}, 500),
So instead of using the buefy's b-autocomplete i want to create and use my own autocomplete component.
So i went ahead and created my own autocomplete component and called it from the DepartmentDetail vue page like this-
DepartmentDetail.vue
<b-field label="Custom Business Unit ">
<AutoComplete :method="getAsyncDataBusinessUnit" title='businessUnit' :autocompleteData="dataBusinessUnit" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
AutoComplete.vue
<template>
<div class="autocomplete">
<input style="font-size: 12pt; height: 36px; width:1800px; " type="text" v-model="this.objectData[this.title]" #input="getAsyncDataBusinessUnit"/>
<ul v-show="isFetching" >
<li v-for="(dataBusinessUnit, i) in dataBusinessUnit" :key="i" #click="setResult(dataBusinessUnit)" >
<!-- {{ autocompleteData }} -->
<template v-if="title!='manager'">
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>Description:</b>
{{dataBusinessUnit.description}}
</p>
</div>
</template>
<template v-else>
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>First Name:</b>
{{dataBusinessUnit.firstName}}
</p>
<p>
<b>Last Name:</b>
{{dataBusinessUnit.lastName}}
</p>
</div>
</template>
</li>
</ul>
</div>
</template>
<script>
import { viewMixin } from "../viewMixin.js";
import schemaData from '../store/schema';
import debounce from "lodash/debounce";
import api from "../store/api";
const ViewName = "AutoComplete";
var passedview;
export default {
name: "AutoComplete",
props: {
method: {
type: Function
},
title: String,
viewname:String,
autocompleteData: {
type: Array,
required: true
}
},
data() {
return {
// results: [],
dataBusinessUnit: [],
isFetching: false
// vignesh: this.objectData[this.title]
};
},
computed: {
viewData() {
return this.$store.getters.getViewData('DepartmentDetail')
},
objectData() {
return this.$store.getters.getApiData(this.viewData.api_id).data
},
sessionData() {
return this.$store.getters.getSessionData()
},
isLoading() {
return this.$store.getters.getApiData(this.viewData.api_id).isLoading
},
newRecord() {
return this.$route.params.id === null;
},
getTitle() {
return this.title
}
},
mounted() {
},
methods: {
setResult(result) {
this.updateValue(result.id,this.title);
// localStorage.setItem(this.title,result.id );
this.isFetching = false;
},
updateValue(newValue, fieldName) {
var val;
var schema = schemaData[this.viewData.schema];
if(typeof schema!=='undefined' && schema['properties'][fieldName]['type'] == 'date'){
val = this.formatDate(newValue);
} else {
val = newValue;
}
this.$store.dispatch('updateDataObjectField', {
key: this.viewData.api_id,
field: fieldName,
value: val
});
},
getAsyncDataBusinessUnit: debounce(function(name) {
console.log('getAsyncDataBusinessUnit you typed'+name.target.value);
if (!name.target.value.length) {
this.dataBusinessUnit = [];
this.isFetching = false;
return;
}
// this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/businessunit/${name.target.value}`)
.then(response => {
this.dataBusinessUnit = [];
if (!response.length)
{
console.log('inside if')
this.isFetching = false;
}
else{
console.log('inside else')
response.forEach(item => {
this.dataBusinessUnit.push(item);
});
this.isFetching = true;
}
console.log('length of dataBusinessUnit is '+(this.dataBusinessUnit).length)
console.log('contents of dataBusinessUnit array '+JSON.stringify(this.dataBusinessUnit))
})
.catch(error => {
this.dataBusinessUnit = [];
throw error;
})
.finally(() => {
// this.isFetching = true;
});
}, 500),
},
components: {
}
};
</script>
The issue iam facing is whenever i start to type anything into the Custom Business Unit input field, then immediately after 1-2 seconds the value gets reset(with the value coming from the store getters ).This however does not happen if i remove the line this.dataBusinessUnit = []; in the getAsyncDataBusinessUnit function . Why is this happening? I even tried using :input instead of v-model for the input field , but i am still facing the same issue.And also second issue is when i click an existing record under DepartmentDetail page, the value that should be set for the custom business unit input field coming from the store getters (this.objectData.businessUnit) is not showing up sometimes? please help
The basic logic of an autocomplete is not really hard:
Vue.component('Autocomplete', {
props: ['list'],
data() {
return {
input: null
}
},
template: `<div><input v-model="input" #input="handleInput"><div class="bordered" v-if="input"><ul><li v-for="(item, i) in list" :key="i" #click="setInput(item)">{{ item }}</li></ul></div></div>`,
methods: {
handleInput(e) {
this.$emit('input', e.target.value)
},
setInput(value) {
this.input = value
this.$emit('input', value)
}
}
})
new Vue({
el: "#app",
computed: {
filteredList() {
if (this.filterInput) {
return this.list.filter(e => e.toLowerCase().indexOf(this.filterInput.toLowerCase()) !== -1)
} else {
return this.list
}
}
},
data: {
filterInput: null,
list: [
"First",
"Second",
"Third",
"Fourth",
"Fifth",
"Sixth",
"Seventh"
]
},
methods: {
handleInput(e) {
this.filterInput = e
}
}
})
.bordered {
border: 1px solid black;
display: block;
}
ul li {
cursor: pointer;
}
ul li:hover {
background: rgba(0, 0, 0, 0.3)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<autocomplete :list="filteredList" #input="handleInput" />
</div>
I wouldn't handle the data in the presentational component (autocomplete is a presentational component in the structure I created), but in its parent-container. That way the autocomplete just gets a list as a prop and displays it; every action is $emitted to the parent, who does the data-handling.
It's easier to control displayed data this way - even with an async data source.

How to close autocomplete dropdown when clicked outside?

How to close autocomplete dropdown when clicked on any outside area? Currently i am calling my custom autocomplete component(child presentation component) 3 times with different labels from another vue page(parent) called departmentDetail.vue.
Example:
departmentDetail.vue
<b-field label="Custom Business Unit ">
<AutoComplete :method="getAsyncDataBusinessUnit" title='businessUnit' :autocompleteData="dataBusinessUnit" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
<b-field label="Custom Managers">
<AutoComplete :method="getAsyncData" title='manager' :autocompleteData="dataManager" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
<b-field label="Custom Location">
<AutoComplete :method=" getAsyncDataLocation" title='location' :autocompleteData="dataLocation" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
AutoComplete.vue (Custom component created by me)
<template>
<div class="autocomplete">
<input style="font-size: 12pt; height: 36px; width:1800px; " type="text" v-model="objectData[title]" #focus="getAsyncDataBusinessUnit" #input="getAsyncDataBusinessUnit"/>
<ul v-show="isFetching" >
<li v-for="(dataBusinessUnit, i) in dataBusinessUnit" :key="i" #click="setResult(dataBusinessUnit)" >
<template v-if="title!='manager'">
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>Description:</b>
{{dataBusinessUnit.description}}
</p>
</div>
</template>
<template v-else>
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>First Name:</b>
{{dataBusinessUnit.firstName}}
</p>
<p>
<b>Last Name:</b>
{{dataBusinessUnit.lastName}}
</p>
</div>
</template>
</li>
</ul>
</div>
</template>
<script>
import { viewMixin } from "../viewMixin.js";
import schemaData from '../store/schema';
import debounce from "lodash/debounce";
import api from "../store/api";
const ViewName = "AutoComplete";
var passedview;
export default {
name: "AutoComplete",
props: {
method: {
type: Function
},
title: String,
viewname:String,
autocompleteData: {
type: Array,
required: true
}
},
data() {
return {
// results: [],
dataBusinessUnit: [],
results: [],
isFetching: false
// vignesh: this.objectData[this.title]
};
},
computed: {
viewData() {
return this.$store.getters.getViewData('DepartmentDetail')
},
objectData() {
return this.$store.getters.getApiData(this.viewData.api_id).data
},
sessionData() {
return this.$store.getters.getSessionData()
},
isLoading() {
return this.$store.getters.getApiData(this.viewData.api_id).isLoading
},
newRecord() {
return this.$route.params.id === null;
},
getTitle() {
return this.title
}
},
mounted() {
},
methods: {
setResult(result) {
this.updateValue(result.id,this.title);
// localStorage.setItem(this.title,result.id );
this.isFetching = false;
},
updateValue(newValue, fieldName) {
var val;
var schema = schemaData[this.viewData.schema];
if(typeof schema!=='undefined' && schema['properties'][fieldName]['type'] == 'date'){
val = this.formatDate(newValue);
} else {
val = newValue;
}
this.$store.dispatch('updateDataObjectField', {
key: this.viewData.api_id,
field: fieldName,
value: val
});
},
getAsyncDataBusinessUnit: debounce(function(name) {
console.log('getAsyncDataBusinessUnit you typed'+name.target.value);
if (!name.target.value.length) {
// this.results = [];
// this.dataBusinessUnit = [...this.results];
// this.isFetching = false;
// return;
}
// this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/businessunit/`,{ filter: `{id}like'%${name.target.value}%'` })
.then(response => {
this.results = [];
if (!response.length)
{
console.log('inside if ')
this.isFetching = false;
}
else{
console.log('inside else')
response.forEach(item => {
this.results.push(item);
});
// this.dataBusinessUnit=this.results
this.dataBusinessUnit = [...this.results];
this.isFetching = true;
}
console.log('length of dataBusinessUnit is '+(this.dataBusinessUnit).length)
console.log('contents of dataBusinessUnit array '+JSON.stringify(this.dataBusinessUnit))
})
.catch(error => {
//this.dataBusinessUnit = [];
throw error;
})
.finally(() => {
// this.isFetching = true;
});
}, 500),
},
components: {
}
};
</script>
Screenshot of the Department screen
And why is it that when the page loads sometimes the values dont show up in these input fields? But upon focus or if i type anything then sudddenly the value shows up?Any idea why this is happening?
About your last question:
"And why is it that when the page loads sometimes the values dont show up in these input fields? But upon focus or if i type anything then sudddenly the value shows up?Any idea why this is happening?"
It looks like you are usesing API requests on computed props.
Computed props are pre renderd values. If your API works async then the computed is renderd "empty" before the full request is resolved.
you could try data props and set them with the API setter in Mounted() or Created().
EDIT:
It could look something like this:
data() {
return {
// results: [],
dataBusinessUnit: [],
results: [],
viewData: [],
objectData:[],
sessionData:[],
isLoading: [],
isFetching: false
// vignesh: this.objectData[this.title]
};
},
computed: {
newRecord() {
return this.$route.params.id === null;
},
getTitle() {
return this.title
}
},
mounted() {
this.viewData = this.$store.getters.getViewData('DepartmentDetail');
this.objectData = this.$store.getters.getApiData(this.viewData.api_id).data;
this.sessionData = this.$store.getters.getSessionData();
this.isLoading = this.$store.getters.getApiData(this.viewData.api_id).isLoading;
},

How delete field of navbar in sql-server and angularjs

I have a navbar containing 4 words (A, B, C, D), when I went to the database and I delete for example (A) by its id, the word (A) deletes in menu but its contents remains displayed, I want deleted also its contents and the page went automatically to the login, how to finish this
controller.js:
User.AccessRole({id:this.user.userId},function (res, req) {
$scope.listmenu=res;
console.log( $scope.listmenu);
//$scope.listmenu.id=remove(listmenu, id);
});
config.js:
.state('aaaa', {
url: "/aaaa",
controller: "aaaa",
templateUrl: "views/aaaa/aaaa.html",
data: { pageTitle: 'aaaa' },
resolve: {
loadPlugin: function ($ocLazyLoad) {
return $ocLazyLoad.load([
'views/aaaa/aaaa.js',
'sweet_alert',
'datatables_lazy',
'fff'
]);
}
},
data: {
role: [roles.SuperAdministrateur,roles.Administrateur,roles.Agent], pageTitle: 'aaaa'
}
})
app.js:
.run(['authService', function (authService) {
authService.fillAuthData();
}])
.run(["$rootScope", "authService", "$state", '$window', function ($rootScope, authService, $state, $window) {
$rootScope.$on("$stateChangeSuccess", function (event, toState) {
var authorizedRoles = toState.data.role;
console.log("from app");
console.log(authorizedRoles);
// authService.authentication.isAuth=true;
if (authService.authentication.isAuth && authService.isAuthorized(authorizedRoles)) {
if (authService.isAuthorized(authorizedRoles) || authorizedRoles === undefined || authorizedRoles.remove(id)) {
console.log("You are authorized");
} else {
$state.go('error.500');
authService.logOut();
}
} else {
$state.go('login');
// $(window).bind('beforeunload', function(){ authService.logOut(); });
}
});
topnavbar.html:
<ul class="clearfix" style="margin-top: 0px">
<span ng-repeat="item in listmenu" >
<li> <a href="#!{{item.MenuLink}}" ng-if="item.menu_id!=7"
class="w3-bar-item w3-button w3-border-right"><center>{{item.designation_menu}}</center></a>
</li>
</span>
</ul>

Angular js $parse does not work

I have written a directive by using typescript. here is my directive below.
'use strict';
module App.Directives {
interface IPageModal extends ng.IDirective {
}
interface IPageModalScope extends ng.IScope {
}
class PageModal implements IPageModal {
static directiveId: string = 'pageModal';
restrict: string = "A";
constructor(private $parse: ng.IParseService) {
}
link = (scope: IPageModalScope, element, attrs) => {
element.click((event) => {
event.preventDefault();
var options = {
backdrop: 'static',
keyboard: false
};
event.openModal = function () {
$('#' + attrs['targetModal']).modal(options);
};
event.showModal = function () {
$('#' + attrs['targetModal']).modal('show');
};
event.closeModal = function () {
$('#' + attrs['targetModal']).modal('hide');
};
var fn = this.$parse(attrs['pageModal']);
fn(scope, { $event: event });
});
}
}
//References angular app
app.directive(PageModal.directiveId, ['$parse', $parse => new PageModal($parse)]);
}
Use in HTML
<button class="btn blue-grey-900" target-modal="emplpyeeViewModal" page-modal="vm.addEmployee($event)">
<i class="icon-plus m-b-xs"></i>
</button>
Use in Controller
addEmployee($event) {
$event.openModal();
};
This line does not work. var fn = this.$parse(attrs['pageModal']); . I can not understand what is wrong. The error is
this.$parse is undefined.
and Service is called two times
It's quite trivial: your this is not your class'es scope because the function openmodal(event) { defines its own.
Declare the function on a class level or use arrow function instead, e.g.
link = (scope: IPageModalScope, element, attrs) => {
element.click((event) => {
event.preventDefault();
var options = {
backdrop: 'static',
keyboard: false
};
event.openModal = function () {
$('#' + attrs['targetModal']).modal(options);
};
event.showModal = function () {
$('#' + attrs['targetModal']).modal('show');
};
event.closeModal = function () {
$('#' + attrs['targetModal']).modal('hide');
};
var fn = this.$parse(attrs['pageModal']);//does not work here
fn(scope, { $event: event });
});
}

How to display a modelpopup window using angularJS?

I need to show a model popup window in a button click.can any one suggest the best method to achieve this in angularjs without BootstrpJS?
I tried the below and is not working. :(
html
<div>
<button ng-click='toggleModal()'>Add Dataset</button>
<modal-dialog info='modalShown' show='modalShown' width='400px' height='60%'>
<p>Modal Content Goes here</p>
</modal-dialog>
</div>
controller
app.controller('DataController', function ($scope,$http) {
$scope.showModal = false;
$scope.toggleModal = function () {
$scope.showModal = !$scope.showModal;
};
$http.get("/api/product").then(function (responses) {
$scope.ProductData = responses.data;
});
.......
........
});
app.directive('modalDialog', function () {
return {
restrict: 'E',
scope: {
show: '=info'
},
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
link: function (scope, element, attrs) {
scope.dialogStyle = {};
if (attrs.width)
scope.dialogStyle.width = attrs.width;
if (attrs.height)
scope.dialogStyle.height = attrs.height;
scope.hideModal = function () {
scope.show = false;
};
},
template: "<div class='ng-modal' ng-show='show'><div class='ng-modal-overlay' ng-click='hideModal()'></div><div class='ng-modal-dialog' ng-style='dialogStyle'><div class='ng-modal-close' ng-click='hideModal()'>X</div><div class='ng-modal-dialog-content' ng-transclude></div></div></div>"
};
});
It looks like you're messing with your scope a little too much. If you check out http://codepen.io/dboots/pen/vLeXPj, I used the same $scope.showModal variable and the same the same $scope.toggleModal function to show/hide.
angular.module('testApp', [])
.controller('DataController', function($scope) {
$scope.showModal = false;
$scope.toggleModal = function() {
$scope.showModal = !$scope.showModal;
};
})
.directive('modalDialog', function() {
return {
restrict: 'E',
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
link: function(scope, element, attrs) {
scope.dialogStyle = {};
if (attrs.width)
scope.dialogStyle.width = attrs.width;
if (attrs.height)
scope.dialogStyle.height = attrs.height;
},
template: "<div class='ng-modal' ng-show='showModal'><div class='ng-modal-overlay' ng-click='toggleModal()'></div><div class='ng-modal-dialog' ng-style='dialogStyle'><div class='ng-modal-close' ng-click='toggleModal()'>X</div><div class='ng-modal-dialog-content' ng-transclude></div></div></div>"
};
});
Make a directive. Then include it in your controller.
See: https://docs.angularjs.org/guide/directive