'click' not working AngularStrap $dropdown service - angularjs

Using AngularStrap. Invoking the $dropdown service from the controller does show the dropdown, but the click on the items does not invoke the respective code.
Plunk to demonstrate this.
http://plnkr.co/edit/tNAX7liFSNh71XcOUecs
var dropdown = $dropdown(element, {
show: false,
trigger: "manual",
html: true
});
dropdown.$scope.content = [
{
"text": "<i class=\"fa fa-globe\"></i> Display an alert",
"click": "alert(\"Holy guacamole!\")"
},
{
"divider": true
},
{
"text": "Separated link",
"href": "#separatedLink"
}
];
element.on("contextmenu", function(event) {
event.preventDefault();
console.log("dropdown right click");
scope.$apply(function() {
scope.dropdown_show = true;
});
});

The alert function you are trying to call should exist in the scope.
try to add below in your controller, just above where you set the content.
dropdown.$scope.alert = function(str){
alert(str)
};

Related

Ionic close IonicPopup when button makes an Ajax call

Am new to both angular and ionic. I have a popup in my page where i show user a input field to enter the OTP and a submit button. When i click on the submit button, I make an Ajax call to check if the OTP is valid.
But am not able to close the popup with .close method. Please help
var OTPPopup = $ionicPopup.show({
title: 'OTP VERIFICATION',
templateUrl: 'templates/login/otp.html',
scope: $scope,
buttons : [{
text: 'Confirm OTP',
type: 'button-assertive',
onTap : function(e) {
e.preventDefault();
var validateResponse = validateOTP();
validateResponse.then(function(response){
console.log('success', response);
return response;
});
}
}]
}).then(function(result){
console.log('Tapped', result);
OTPPopup.close();
});
And below is the function validateOTP
function validateOTP() {
var requestObj = {
authentication: {
email_id: $scope.loginForm.email,
security_code: $scope.OTP
}
};
return $q(function(resolve, reject) {
activateUser(requestObj, function(response){
if(response.error == null && response.data.isSuccess) {
console.log('validate correct');
resolve(response);
}
}, function(response){
return 'error';
});
});
}
activateUser is my service which makes the ajax call. Please let me know how can i acheive this.
console.log('success', response) is being printed inside the .then but after returning something from the onTap , the promise of the popup is not being called.
Ended up solving it myself.
This solution would work only if you have exactly one ionicPopup on your page. I just wrote this line of code to do the trick
$ionicPopup._popupStack[0].responseDeferred.resolve();
This automatically closes the popup. The whole code is more simpler now with normal Ajax without any q promises.
var OTPPopup = $ionicPopup.show({
title: 'OTP VERIFICATION',
templateUrl: 'templates/login/otp.html',
scope: $scope,
buttons : [{
text: 'Confirm OTP',
type: 'button-assertive',
onTap : function(e) {
// e.preventDefault() will stop the popup from closing when tapped.
e.preventDefault();
validateOTP();
}
}]
});
and in the next function
function validateOTP() {
var requestObj = {
authentication: {
email_id: $scope.loginForm.email,
security_code: $scope.loginForm.OTP
}
};
activateUser(requestObj, function(response){
if(response.error == null && response.data.isSuccess) {
localStorage.setLocalstorage = response.data.user[0];
$ionicPopup._popupStack[0].responseDeferred.resolve();
$state.go('dashboard.classified');
}
}, function(response){
});
}
you don't need call e.preventDefault();
you just only return the validateOTP promise
ionicPopup will waiting the promise then close popup
var OTPPopup = $ionicPopup.show({
title: 'OTP VERIFICATION',
template: 'templates/login/otp.html',
scope: $scope,
buttons : [{
text: 'Confirm OTP',
type: 'button-assertive',
onTap : function() {
return validateOTP();
}
}]
}).then(function(result){
console.log('closed', result); // result is the activateUser resolve response
});

reuse Bootsrap Modal view in BackboneJS

I just started to learn BackboneJS and getting deeper inside I face a problem. I have a bootstrap modal where I would like populate the modal-content in function of a called event fired in my main view and try to figure out how to inject a subview in my Modal view which is dynamically generated. So far my code looks like but not working
Main view
//here events are mapped
Fefe.Views = Fefe.Views || {};
(function () {
'use strict';
Fefe.Views.Editor = Backbone.View.extend({
template: JST['app/scripts/templates/editor.ejs'],
tagName: 'div',
el: '.container',
id: '',
className: '',
events: {
"click button.expand" : "controlToggle",
"click .grid" : "grid"
},
controlToggle: function(e){
var controlls = $(e.currentTarget).closest('.editor-controls')
$(controlls).find('.active').removeClass('active')
$(e.currentTarget).parent().addClass('active')
},
grid: function() {
this.model = new Fefe.Models.Grids({
'title': 'Edit Grids'
})
var gridView = new Fefe.Views.Grids({
model: this.model
})
var grids = new Fefe.Views.Modal({
model : this.model,
subview: gridView
}).render()
},
initialize: function () {
var body = $('body')
var rows = body.find('.row')
$.each(rows, function(e , v){
$(this).addClass('editor-row empty-row')
})
$('.sortable-rows').sortable({ handle: 'button.row-handle.btn.btn-default' })
this.listenTo(this.model, 'change', this.render);
},
render: function () {
return this;
}
});
})();
Modal view
//this one holds the modal markup
Fefe.Views = Fefe.Views || {};
(function () {
'use strict';
Fefe.Views.Modal = Backbone.Marionette.View.extend({
template: JST['app/scripts/templates/modal.ejs'],
subview: '',
className: "modal",
attributes: {
tabindex: "-1",
role: "dialog",
},
initialize: function() {
this.template = this.template;
console.log(this)
},
events: {
"click .save": "save",
"click .close": "close",
"change input": "modify",
},
render: function(e) {
this.$el.html(this.template(this.model.toJSON())).modal()
$(".modal-dialog").draggable({
handle: ".modal-header"
})
return this
},
show: function() {
$(document.body).append(this.render().el);
},
close: function() {
this.remove();
},
save: function() {
if(this.model.id == null) {
tasks.create(this.model);
}
else {
this.model.save();
}
this.remove();
},
edit: function(e) {
var attribute = {};
attribute[e.currentTarget.name] = e.currentTarget.value;
this.model.set(attribute);
},
});
})();
Maybe the approach is wrong and I'm on the wrong track
You should checkout the way with custom regions, described by Brian Mann at backbonerails.com
So the idea is following:
1) Define a region in your app with special class, lets call it DialogRegion
regions: {
dialogs: {
selector: '#dialogs',
regionClass: DialogRegion
}
}
2) Extend DialogRegion like following. I used Bootstrap modal API, please expect
var DialogRegion = Marionette.Region.extend({
onShow: function(view) {
view.$el.addClass('modal');
view.$el.modal();
// add handler to close popup via event
view.on('before:destroy', function() {
view.$el.modal('hide');
});
//destroy view on popup close
view.$el.on('hidden.bs.modal', function (e) {
view.destroy();
});
})
})
3) Later from any place of your app you can render Modal via rendering any view in dialogs App region:
App.dialogs.show( new SomeSuperView({
model: model
}))
I recommend you to checkout tutorial at Backbonerails to clarify this way. Hope you will find it usefull

Angular UI Modal, avoid modal scope change reflects in parent scope

I am building an Angular app where I want to control which apps in app list I want to show on home page. There is a section called "Manage Apps" where I can manage visible apps..
http://plnkr.co/edit/RPFvv0ZUB2OSctIQM8pQ?p=preview
The plunkr above explains what I want to achieve..
I have passed list of apps in json from parent scope to modal instance. I want to make changes to one field there which is IsPublished.
Now the problem is, whenever I make changes in isPublished field in Modal, it immediately gets reflected in parent scope. You can see apps being filtered in parent scope behind overlay..
I want to avoid it. I want to reflect the changes to parent scope only when I hit save / ok button.
is there any way to do so?
You need a deep copy of a source use angular.copy.The changes directly reflected to main screen because you bind $scope.apps with $scope.items and hence both are refrencing to the same location.
angular.module('ui.bootstrap.demo', ['ui.bootstrap']);
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $modal, $log) {
$scope.items = ['item1', 'item2', 'item3'];
$scope.apps = [
{
"FileSystemObjectType":0,
"Id":1,
"ContentTypeId":"0x01008178C725CC128447AD122168CA03E484",
"Title":"First App",
"AppUrl":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"http://www.google.com",
"Url":"http://www.google.com"
},
"AppIcon":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"https://unsplash.it/150/?random",
"Url":"https://unsplash.it/150/?random"
},
"CanDelete":false,
"IsPublished":false,
"Priority":null,
"ID":1,
"Modified":"2015-03-04T15:44:36Z",
"Created":"2015-02-26T05:24:00Z",
"AuthorId":9,
"EditorId":9,
"OData__UIVersionString":"1.0",
"Attachments":false,
"GUID":"5a3e620d-461c-4663-8837-36bfd2967dad"
},
{
"FileSystemObjectType":0,
"Id":2,
"ContentTypeId":"0x01008178C725CC128447AD122168CA03E484",
"Title":"App 2",
"AppUrl":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"http://microsoft.com",
"Url":"http://microsoft.com"
},
"AppIcon":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"https://unsplash.it/150/?random",
"Url":"https://unsplash.it/150/?random"
},
"CanDelete":true,
"IsPublished":false,
"Priority":null,
"ID":2,
"Modified":"2015-03-04T15:44:36Z",
"Created":"2015-02-26T05:25:11Z",
"AuthorId":9,
"EditorId":9,
"OData__UIVersionString":"1.0",
"Attachments":false,
"GUID":"e919eb66-0f2b-4ed4-aad9-3b64400bf09a"
},
{
"FileSystemObjectType":0,
"Id":3,
"ContentTypeId":"0x01008178C725CC128447AD122168CA03E484",
"Title":"App 3",
"AppUrl":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"http://google.com",
"Url":"http://google.com"
},
"AppIcon":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"https://unsplash.it/150/?random",
"Url":"https://unsplash.it/150/?random"
},
"CanDelete":true,
"IsPublished":true,
"Priority":0,
"ID":3,
"Modified":"2015-03-04T15:44:36Z",
"Created":"2015-02-26T08:06:23Z",
"AuthorId":9,
"EditorId":9,
"OData__UIVersionString":"1.0",
"Attachments":false,
"GUID":"07a63d11-fe94-4fc2-95fc-b7ddf16f160a"
},
{
"FileSystemObjectType":0,
"Id":4,
"ContentTypeId":"0x01008178C725CC128447AD122168CA03E484",
"Title":"Test1",
"AppUrl":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"http://www.attini.com",
"Url":"http://www.attini.com"
},
"AppIcon":{
"__metadata":{
"type":"SP.FieldUrlValue"
},
"Description":"https://unsplash.it/150/?random",
"Url":"https://unsplash.it/150/?random"
},
"CanDelete":true,
"IsPublished":true,
"Priority":1,
"ID":4,
"Modified":"2015-03-04T15:44:36Z",
"Created":"2015-02-27T03:58:37Z",
"AuthorId":9,
"EditorId":9,
"OData__UIVersionString":"1.0",
"Attachments":false,
"GUID":"9375eff9-4101-4c1f-ad85-bedc484b355f"
}
];
$scope.open = function (size) {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $scope.apps;
}
}
});
modalInstance.result.then(function (items) {
$scope.apps = angular.copy(items);
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
});
// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $modalInstance, items) {
$scope.items = angular.copy(items);
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function () {
$modalInstance.close($scope.items);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});
Working Plunker

Multiple instances of custom directive, confusion with ngModel

I've created a custom directive to render a slider for a question (essentially wrapping jquery ui slider). The directive takes an ngModel and updates it when the user uses the slider, and there's a $watch attached to the parent model (the ngModel passed to the directive is only a part of a parent model). The directive has multiple instances on a page.
I've encountered an issue with the watch, as it seems as the watch always occurs on the last question on the page. So for example a page with 10 question, using the slider on question 1 - triggers the watch on the last question (question 10). I believe the issue has something to do with directives/isolated scope and/or the watch function, but I'm unable to solve it.
this.app.directive('questionslider', () => {
var onChangeEvent = (event, ui) => {
updateModel(ui.value);
};
var onSlideEvent = (event, ui) => {
updateUi(event, ui);
};
var updateUi = (event, ui) => {
$(ui.handle).find(".ff-handle-glyph > div").css("top", (ui.value) * - 10);
}
var updateModel = (newValue) => {
// find value in values list...
angular.forEach(isolatedScope.model.PossibleValues, function(value) {
if (parseInt(value.Name) === newValue) {
isolatedScope.$apply(function() {
isolatedScope.model.Value = value;
});
}
});
};
var isolatedScope: any;
return {
restrict: 'AE',
replace: true,
template: '<div></div>',
scope: {
model: '=ngModel',
},
link: function(scope, element, attrs, ctrl) {
isolatedScope = scope;
scope.$watch(ngModelCtrl, function() {
// use provided defaultValue if model is empty
var value = isolatedScope.model.Value === null ? isolatedScope.model.DefaultValue : isolatedScope.model.Value;
element.slider({
min: 0,
max: isolatedScope.model.PossibleValues.length,
value: value.Name,
change: onChangeEvent,
slide: onSlideEvent
});
}
}
};
};
Code to add watch in controller
this.$scope.questions.forEach(function(question) {
this.$scope.$watch(
function() { return question; },
function(newVal, oldVal) { this.updateQuestion(newVal, oldVal) },
true
);
});
UpdateQuestion function (right now just outputting current question)
function updateQuestion(newVal, oldVal) {
// prevent event on initial load
if (newVal === oldVal) {
return;
}
console.log(newVal);
}
The ng-repeat markup instantiating questionsliders
<div data-ng-repeat="question in Questions">
<h4>{{question.QuestionText}}</h4>
<p>{{question.RangeMinText}}</p>
<questionslider ng-model="question"></questionslider>
<p>{{question.RangeMaxText}}</p>
</div>
Question JSON would look like this
{
"DefaultValue": {
"Id": "5",
"Name": "5"
},
"Id": "1",
"IsAnswered": false,
"PossibleValues": [
{
"Id": "1",
"Name": "1"
},
{
"Id": "2",
"Name": "2"
},
{
"Id": "3",
"Name": "3"
},
{
"Id": "4",
"Name": "4"
},
{
"Id": "5",
"Name": "5"
},
{
"Id": "6",
"Name": "6"
},
{
"Id": "7",
"Name": "7"
},
{
"Id": "8",
"Name": "8"
},
{
"Id": "9",
"Name": "9"
},
{
"Id": "10",
"Name": "10"
}
],
"QuestionText": "hows it haning?",
"RangeMaxText": "not good",
"RangeMinText": "Very good",
"Type": 0,
"Value": null
}
],
"Title": "Question title",
"Type": 0
}
So issue is, no matter which question I update with the slider directive, it's always the last on page passed into updateQuestion.
UPDATE
I tried using $watchCollection, but nothing seems to fire the event.
this.$scope.$watchCollection(
'questions',
function (newVal, oldVal) {
// prevent event on initial load
if (newVal === oldVal) {
return;
}
for (var i = 0; i < newVal.length; i++) {
if (newVal[i] != oldVal[i]) {
this.$log.info("update : " + newVal.Id);
}
}
}
);
I also tried with
function() { return questions; }
as first expressions. Still no luck.
Maybe using individual controllers for each question is my only option, but it seems a bit of a workaround.
UPDATE
So i tried using individual controllers for each question, adding a watch per question in the controller, and the strange thing is that even this is reproducing same scenario. It's still the last question on the page passed into the watch function.
markup
<div data-ng-repeat="question in Questions" data-ng-controller="QuestionInstanceController">
<h4>{{question.QuestionText}}</h4>
<p>{{question.RangeMinText}}</p>
<questionslider ng-model="question"></questionslider>
<p>{{question.RangeMaxText}}</p>
</div>
Controller code
app.controller('QuestionInstanceController', function($scope) {
console.log($scope.question); // outputs correct question here!
$scope.$watch(
function() { return $scope.question; },
function(newValue, oldValue) {
console.log(newValue); // always outputs last question on page
},
true);
}
}
It must have something to do with my custom directive, am i somehow overwriting previous instances when having multiple instances on the page?
So i managed to find the solution myself. The issue was that i had a "var isolatedScope;" in the directive which was assigned on every run of the link(). I thought vars declared in the directive were isolated on each instance but infact they are overwritten on every implementation of the directive.
The solution therefore was to move the implementation of onChange directly to the init of the slider component, and thus avoiding the need of access to the scope variable later
this.app.directive('questionslider', () => {
var onChangeEvent = (event, ui) => {
};
var onSlideEvent = (event, ui) => {
updateUi(ui.value, ui.handle);
};
var updateUi = (value, element) => {
}
var onStartSlide = (event, ui) => {
}
var onEndSlide = (event, ui) => {
}
return {
restrict: 'AE',
require: 'ngModel',
replace: true,
templateUrl: 'templates/questionSlider.html',
scope: {
model: '=ngModel'
},
link: (scope: any, element, attrs, ngModelCtrl: ng.INgModelController) => {
scope.$watch(ngModelCtrl, () => {
// use provided defaultValue if model is empty
var value = scope.model.Value === null ? scope.model.DefaultValue : scope.model.Value;
var hasNoValue = scope.model.Value === null;
if (hasNoValue) {
element.find('.ui-slider-handle').addClass('dg-notset');
}
element.slider({
min: parseInt(scope.model.PossibleValues[0].Name),
max: scope.model.PossibleValues.length,
value: parseInt(value.Name),
slide: onSlideEvent,
start: onStartSlide,
stop: onEndSlide,
animate: true,
change: (event, ui) => {
// find value in values list...
angular.forEach(scope.model.PossibleValues, (val) => {
if (parseInt(val.Name) === ui.value) {
scope.$apply(() => {
scope.model.Value = val;
});
}
});
onChangeEvent(event, ui);
}
});
});
}
};
});
I expect that your $scope.$watch logic is throwing you off. The simplest way might be to put a watch on the entire array of questions using $watchCollection:
$scope.$watchCollection(questions, function(newValue, oldValue) {
for(index=0; index<newValue.length; index++) {
if (newValue[index] !== oldValue[index]) {
console.log("Question " + index + " updated to: " + newValue[index]);
}
}
});
Otherwise, you could probably create a separate controller for each item in your ng-repeat loop and have a watch there that deals with the change. I don't love this solution, either, as it's kinda long-winded. First, a new controller for dealing with the questions:
app.controller('QuestionCtrl', function($scope) {
$scope.$watch('question', function(newValue, oldValue) {
if (newVal === oldVal) {
return;
}
console.log(newVal);
}
});
With your view slightly modified to include a reference to the new QuestionCtrl:
<div data-ng-repeat="question in Questions" ng-controller='QuestionCtrl'>
<h4>{{question.QuestionText}}</h4>
<p>{{question.RangeMinText}}</p>
<questionslider ng-model="question"></questionslider>
<p>{{question.RangeMaxText}}</p>
</div>
This article gives more information about using controllers with ng-repeat.
I hope one of these helps you out.

$rootScope.$apply() ignores variable assignment

In my Service I have this code:
setInterval(function () {
$rootScope.$apply(function () {
cart.push({
"Id": 4,
"Name": "Some item",
"Price": 4
});
});
}, 3000);
Working example: http://jsfiddle.net/ko8edf32/7/
This works correctly: the cart change is pushed to the view.
However, when I assign a new value to cart, the change is NOT pushed to the view:
setInterval(function () {
$rootScope.$apply(function () {
var newcart = [{
"Id": 4,
"Name": "Some item " + Math.random(),
"Price": 4
}];
cart = newcart;
});
}, 3000);
example: http://jsfiddle.net/ko8edf32/8/
What is the reason of this?
What is the best/most elegant solution to solve this issue?
EDIT
This is the working solution I've built, based on the answers below:
jsfiddle.net/ko8edf32/11
I've changed your cart into more "angular" way with two-way databinding. I've added the second controller to show you how nice it works all together without getters/setters and generally with a bit of magic
http://plnkr.co/edit/U5pUlAPJTNd9V0baSjAu?p=preview
homeApp.factory("cartService", function($rootScope) {
var service = null
service = {
all: function() {
return service.cart;
},
add: function(item) {
service.total += item.Price
service.cart.push(item);
},
remove: function(item) {
service.total -= item.Price
service.cart.splice(service.cart.indexOf(item), 1);
},
cartUpdated: function(newValue) {
service.cart = newValue;
},
cart: [],
total: 0
}
return service;
});
You have to use objects on $scope.
$scope.model = { cart: [] };
Here is a working example: http://jsfiddle.net/56vkqygn/2/
Here is a explanation: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

Resources