Writing a test to test my delete functionality for my app. I created a mock delete $modal to simulate cancelling/confirming deletion.
var modalInstanceMock=
{
result: {
then: function(confirmCallback, cancelCallback) {
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
}
},
confirmCallBack: function(item){
return true;
},
cancelCallback: function(type){
return false;
},
close: function( item ) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.result.confirmCallBack( item );
},
dismiss: function( type ) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.result.cancelCallback( type );
}
};
I do this before each test:
beforeEach(inject(function($modal) {
spyOn($modal, 'open').andReturn(modalInstanceMock);
}));
This works perfectly:
var newRes = scope.deleteCar(car);
scope.modalInstance.close("ok");
However when I try this:
var newRes = scope.deleteCar(car);
scope.modalInstance.dismiss("ok");
I get a Type:error undefined is not a function at Object.modalInstanceMock.dismiss.
Can't understand what is going wrong when close works fine.
Initilize in beforeEach,
modalInstance = {
close: jasmine.createSpy('modalInstance.close'),
dismiss: jasmine.createSpy('modalInstance.dismiss')
},
and then expect.
Related
The loading modal is created correctly, but when the finally block is hit it does not close it. Is there any known reason for this? The loading time is minimal but I still need it for cases where there is a delay. I am testing with a device and in Chrome - The issue only arises when it is being run in Chrome.
$scope.init = function() {
var dialog = Modals.openLoadingModal();
OfflineManager.getTemplates().then(function(templates) {
$scope.templates = templates.map(function(e) {
// get e
return e;
});
OfflineManager.getInspections().then(function(inspections) {
$scope.inspections = inspections.map(function(e) {
// get e
return e;
});
}).finally(function() {
dialog.close();
});
});
};
The modal view:
<div class="loadingModal">
<data-spinner data-ng-init="config={color:'#fff', lines:8}" data-config="config"></spinner>
</div>
The modal service:
this.openLoadingModal = function(callback) {
var opts = {
backdrop: true,
backdropClick: false,
keyboard: false,
templateUrl: 'views/modals/loading.html'
};
return this.open(opts, callback, null);
};
this.open = function(opts, closeHandler, dismissHandler, model) {
opts.resolve = { modalModel:function() { return model; }};
opts.controller = opts.controller || 'ModalController';
$('div, input, textarea, select, button').attr('tabindex', -1);
var modalInstance = $modal.open(opts);
modalInstance.result.then(function(result) {
$('div, input, textarea, select, button').removeAttr('tabindex');
if (closeHandler) {
closeHandler(result);
}
}, function(result) {
$('div, input, textarea, select, button').removeAttr('tabindex');
if (dismissHandler) {
dismissHandler(result);
}
});
return modalInstance;
};
After some searching I found the following solution which waits until the modal has finished opening before executing:
.finally(function() {
dialog.opened.then(function() {
dialog.close();
});
});
Source:
Call function after modal loads angularjs ui bootstrap
Per the ui.bootstrap docs - http://angular-ui.github.io/bootstrap/versioned-docs/0.13.3/#/modal
result - a promise that is resolved when a modal is closed and rejected when a modal is dismissed
It looks like you're trying to use the wrong promise to execute your logic. result gets triggered as a product of calling $modalInstance.close or $modalInstance.dismiss. If you're trying to close your modal programmatically (as opposed to closing via ng-click within the modal template/controller) you need to call $modalInstance.close or $modalInstance.dismiss directly, then your result.then will execute.
I was wondering how to use page object variable on .each() function.
The scenario is every I click delete link, the sweet alert confirmation will be shown, and I must confirm that dialog to delete the data.
Here is my page object:
'use strict';
// page object name
var Data = function()
{
// all delete links
this.delete_links = element.all(by.css('div[ng-click="delete(Data.id)"]'));
// confirm button
this.btn_confirm = element(by.css('.confirm'));
// delete function
this.delete = function()
{
// delete all links with confirmation
this.delete_links.each(function(element, index)
{
// click delete link
element.click().then(function()
{
browser.sleep(1000);
});
// click yes
this.btn_confirm.click().then(function()
{
browser.sleep(1000);
});
});
};
};
module.exports = Data;
this inside the "each" function/callback does not refer to the page object itself. To fix it, define a variable and set it to this.btn_confirm:
this.delete = function()
{
// delete all links with confirmation
this.delete_links.each(function(element, index)
{
var confirmButton = this.btn_confirm;
// click delete link
element.click().then(function()
{
browser.sleep(1000);
});
// click yes
confirmButton.click().then(function()
{
browser.sleep(1000);
});
});
};
I have one form for saving and editing records. On clicking on a record, the form should be filled with the data. After filling, I want to do some UI actions (call jQuery Plugin etc.).
The pre-filling works, but when I'm trying to access the values, it works only at the second click. On the first click, the values are empty or the ones from the record clicked before.
This action is stored in the controller:
edit: function(id) {
var _this = this;
// prefill form for editing
var customer = this.store.find('customer', id).then(function(data) {
_this.set('name',data.get('name'));
_this.set('number',data.get('number'));
_this.set('initial',data.get('initial'));
_this.set('description',data.get('description'));
_this.set('archived',data.get('archived'));
// store user for save action
_this.set('editedRecordID',id);
_this.set('isEditing',true);
$('input[type="text"]').each(function() {
console.log(this.value)
});
});
},
I need a generic way to check if the input field is empty, because I want to include this nice UI effect: http://codepen.io/aaronbarker/pen/tIprm
Update
I tried to implement this in a View, but now I get always the values from the record clicked before and not from the current clicked element:
View
Docket.OrganizationCustomersView = Ember.View.extend({
didInsertElement: function() {
$('input[type="text"]').each(function() {
console.log(this.value)
});
}.observes('controller.editedRecordID')
});
Controller
Docket.OrganizationCustomersController = Ember.ArrayController.extend({
/* ... */
isEditing: false,
editedRecordID: null,
actions: {
/* ... */
edit: function(id) {
var _this = this;
// prefill form for editing
var customer = this.store.find('customer', id).then(function(data) {
_this.set('name',data.get('name'));
_this.set('number',data.get('number'));
_this.set('initial',data.get('initial'));
_this.set('description',data.get('description'));
_this.set('archived',data.get('archived'));
// store user for save action
_this.set('editedRecordID',id);
_this.set('isEditing',true);
});
},
/* ... */
});
Update 2
OK, I think I misunderstood some things.
At first, my expected console output should be:
1.
2.
3.
but is:
1.
3.
2.
Secondly: I can use any name, even foobar, for the observed method in my view. Why?
Controller
edit: function(id) {
var _this = this;
// prefill form for editing
var customer = this.store.find('customer', id).then(function(data) {
_this.set('name',data.get('name'));
_this.set('number',data.get('number'));
_this.set('initial',data.get('initial'));
_this.set('description',data.get('description'));
_this.set('archived',data.get('archived'));
console.log('1.')
// store user for save action
_this.set('editedRecordID',id);
_this.set('isEditing',true);
console.log('2.')
});
},
View
Docket.OrganizationCustomersView = Ember.View.extend({
foobar: function() {
console.log('3.')
$('input[type="text"]').each(function() {
console.log(this.value)
});
}.observes('controller.editedRecordID')
});
Update 3
I think I "figured it out" (but I don't know why):
Docket.OrganizationCustomersView = Ember.View.extend({
movePlaceholder: function() {
$('input[type="text"], textarea').bind("checkval",function() {
var $obj = $(this);
setTimeout(function(){
console.log($obj.val());
},0);
}.observes('controller.editedRecordID')
});
setTimeout(function(){ ... }, 0); does the trick. But why?!
You can convert use that jquery code in a component, this is the best way to create a reusable view, without putting ui logic in controllers, routers etc.
Template
<script type="text/x-handlebars" data-template-name="components/float-label">
<div class="field--wrapper">
<label >{{title}}</label>
{{input type="text" placeholder=placeholder value=value}}
</div>
</script>
FloatLabelComponent
App.FloatLabelComponent = Ember.Component.extend({
onClass: 'on',
showClass: 'show',
checkval: function() {
var label = this.label();
if(this.value !== ""){
label.addClass(this.showClass);
} else {
label.removeClass(this.showClass);
}
},
label: function() {
return this.$('input').prev("label");
},
keyUp: function() {
this.checkval();
},
focusIn: function() {
this.label().addClass(this.onClass);
},
focusOut: function() {
this.label().removeClass(this.onClass);
}
});
Give a look in that jsbin http://emberjs.jsbin.com/ILuveKIv/3/edit
I am attempting to implement a Pub/Sub pattern in jQuery with the following code :
$.each({
trigger : 'publish',
on : 'subscribe',
off : 'unsubscribe'
}, function ( key, val) {
jQuery[val] = function() {
o[key].apply( o, arguments );
};
});
This works fine until I attempt to build something with multiple instances.
I have an activity object that is applied to each $('.activity_radio') div element. When I click on a radio button inside any $('.activity_radio') div the $.subscribe event will trigger (X) amount of times based on the number of activity_radio divs on are on the page.
How do I publish/subscribe events based only within a particular div?
Code
Radio Activity ( radio-activity.js )
var activity = {
init : function ( element ) {
// get our boilerplate code
this.activity = new util.factories.activity();
this.element = element;
this.$element = $(element);
// other init code
// gather our radio elements
this.target_element = this.$elem.find('input[type=radio]');
// send our radio elements to onSelect
this.activity.onSelect(this.target_element);
// trigger click function that will subscribe us to onSelect publish events
this.click()
},
// subscribe to events
click : function()
{
$.subscribe('activity.input.select', function ( event, data ){
// we have access to the value the user has clicked
console.log(data);
// trigger another function // do something else
});
}
}
Base Activity Boilerplate Code ( activity-factory.js )
var activity_factory = factory.extend({
init: function(e)
{
// init code
},
onSelect : function ( inputs ) {
inputs.on('click', function(){
// do some processing
// retrieve the value
var data = $(this).val();
// announce that the event has occured;
$.publish( 'activity.input.select', data );
});
}
}
});
Triggered when DOM is ready
$(function(){
// foreach DOM element with the class of activity_radio
$('.activity_radio').each(function(){
// trigger the init func in activity object
activity.init(this);
});
});
You can write your subscribe/publish as a plugins
$.each({
trigger : 'publish',
on : 'subscribe',
off : 'unsubscribe'
}, function ( key, val) {
jQuery.fn[val] = function() {
this[key].apply(this, Array.prototype.slice.call(arguments));
};
});
And you will be able to call it on $element
this.$element.subscribe('activity.input.select', function(event, data) {
and
onSelect: function ( inputs ) {
var self = this;
inputs.on('click', function(){
// do some processing
// retrieve the value
var data = $(this).val();
// announce that the event has occured;
self.$element.publish('activity.input.select', data);
});
}
I want to try to override window.confirm function with modal dialog.
<a href="http://example.com" onClick="return confirm('you want to go?')">
<script>
window.confirm = function(message){
$("#confirm-dialog").modal('show');
$("#confirm-dialog .modal-body p").html(message);
$("#confirmYes").on("click", function () {
return true;
});
}
</script>
When I click in modal window on the #confirmYes element it returns true, but the redirect by href link will not work...Why?
Can somebody tell me how I can do this thing without changing my link?
Thanks
UPD
Yii framework generates that code for CGridView widget and i want to override it. I can't change this code, because its in framework. Instead this confirm standard i want to use my modal window
$(document).on('click','#product-grid a.delete',function() {
if(!confirm('Are you sure you want to delete this item?')) return false;
var th=this;
var afterDelete=function(){};
$.fn.yiiGridView.update('product-grid', {
type:'POST',
url:$(this).attr('href'),
success:function(data) {
$.fn.yiiGridView.update('product-grid');
afterDelete(th,true,data);
},
error:function(XHR) {
return afterDelete(th,false,XHR);
}
});
return false;
});
Here is a practice that we used in my company in a UI conversion project.
It is ugly though, but it works just fine.
var clickState={};
var justClicked=null;
window.confirm = function(message) {
var e = window.event || window.confirm.caller.arguments[0];
var el = e.target || e.srcElement; // the element's click that triggers confirm dialog
if(justClicked && clickState[justClicked]===true){
clickState[justClicked]=false;
return true;
}else{
// your async style confirmation dialog (e.g. jQuery's dialog)
showConfirmBox(message, function() {
justClicked=el;
clickState[el]=true;
$(justClicked).click(); // in the call back function , click the target again.
});
}
return false;
};
Js default confirm dialog work synchronously, it means that code will wait for the user to make his choice to continue. When you override confirm dialog this way what happens is that your new stylish confirm dialog is shown but method end immediately and returns undefined.
You can work with callbacks;
<a href="http://example.com" onClick="confirm('you want to go?', function(result){
if(result)
//code to redirect, like window.location(this.href);
}); return false;">
then:
<script>
window.confirm = function(message, cb){
$("#confirm-dialog").modal('show');
$("#confirm-dialog .modal-body p").html(message);
$("#confirmYes").on("click", function (userChoice) {
cb(userChoice); //true or false - your jquery plugin will supply this value
});
}
</script>
EDIT: Its important to keep the link url on href (instead of just leaving "/#") for SEO reasons - for the link not be triggered you should also return false after calling your new confirm dialog.
If you need Overriding the window.alert() dialog box you can find it here
after that I have create my own Overriding the window.confirm() dialog box you can find it here
Overriding the window.confirm() dialog box.
It is pretty simple just like:
window.confirm = function(message, title, doYes) {
$(document.createElement('div'))
.attr({title: title, class: 'confirm'})
.html(message)
.dialog({
buttons: {
"Confirm": function() {
$(this).dialog("close");
if (doYes && (typeof doYes === "function")) {
doYes();
}
},
"Cancel": function() {
$(this).dialog("close");
}
}
,
close: function() {
$(this).remove();
},
draggable: true,
modal: true,
resizable: false,
width: 'auto'
});
};
// New confirm
//confirm('This is a <strong>new</strong> alert!','Confirm', function(){alert('Yes')},function(){alert('No')});
I know it is an old post but I want to share my solution, I know this changes the yii default behaviour but I replaced their function for one custom working the same way, I am gonna ask yii gurus about a better way or if in the future this can be done easily.
In framework/yii/zii/widgets/grid/CButtonColumn.php modify the initDefaultButtons:
/**
* Initializes the default buttons (view, update and delete).
*/
protected function initDefaultButtons()
{
if($this->viewButtonLabel===null)
$this->viewButtonLabel=Yii::t('zii','View');
if($this->updateButtonLabel===null)
$this->updateButtonLabel=Yii::t('zii','Update');
if($this->deleteButtonLabel===null)
$this->deleteButtonLabel=Yii::t('zii','Delete');
if($this->viewButtonImageUrl===null)
$this->viewButtonImageUrl=$this->grid->baseScriptUrl.'/view.png';
if($this->updateButtonImageUrl===null)
$this->updateButtonImageUrl=$this->grid->baseScriptUrl.'/update.png';
if($this->deleteButtonImageUrl===null)
$this->deleteButtonImageUrl=$this->grid->baseScriptUrl.'/delete.png';
if($this->deleteConfirmation===null)
$this->deleteConfirmation=Yii::t('zii','Are you sure you want to delete this item?');
foreach(array('view','update','delete') as $id)
{
$button=array(
'label'=>$this->{$id.'ButtonLabel'},
'url'=>$this->{$id.'ButtonUrl'},
'imageUrl'=>$this->{$id.'ButtonImageUrl'},
'options'=>$this->{$id.'ButtonOptions'},
);
if(isset($this->buttons[$id]))
$this->buttons[$id]=array_merge($button,$this->buttons[$id]);
else
$this->buttons[$id]=$button;
}
if(!isset($this->buttons['delete']['click']))
{
if(is_string($this->deleteConfirmation))
$confirmation="if(!confirm(".CJavaScript::encode($this->deleteConfirmation).")) return false;";
else
$confirmation='';
if(Yii::app()->request->enableCsrfValidation)
{
$csrfTokenName = Yii::app()->request->csrfTokenName;
$csrfToken = Yii::app()->request->csrfToken;
$csrf = "\n\t\tdata:{ '$csrfTokenName':'$csrfToken' },";
}
else
$csrf = '';
if($this->afterDelete===null)
$this->afterDelete='function(){}';
$withConfirmation = strlen($confirmation) == 0 ? 0 : 1;
$confirmationMessage = CJavaScript::encode($this->deleteConfirmation);
$this->buttons['delete']['click']=<<<EOD
`function(event) {
event.preventDefault();
if ($withConfirmation){
var th=this;
var afterDelete=$this->afterDelete;
var deleteUrl=$(this).attr('href');
console.log(deleteUrl);
$(document.createElement('div')).attr({
title:'AtenciĆ³n',
'class': 'dialog'
}).html($confirmationMessage).dialog({
buttons: {
"OK": function () {
$(this).dialog('close');
$.fn.yiiGridView.update('{$this->grid->id}', {
type:'POST',
url:deleteUrl,$csrf
success:function(data) {
$.fn.yiiGridView.update('{$this->grid->id}');
afterDelete(th,true,data);
},
error:function(XHR) {
return afterDelete(th,false,XHR);
}
});
return true;
},
"Cancel": function () {
$(this).dialog('close');
return false;
}
},
close: function () {
$(this).remove();
},
draggable: false,
modal: true,
resizable: false,
width: 'auto'
}).position({
my: "center",
at: "center",
of: window
});
}
}
EOD;
}
}
`
EDIT:
I also learned how to do it without modify core yii:
In you widget grid you a file for the buttons like this:
array
(
'class'=>'CButtonColumn',
'deleteConfirmation'=>'Atencion',
'buttons'=>array
(
'update'=>array
(
'imageUrl'=>FALSE,
'label'=>'update',
'options'=>array('title'=>'update'),
'visible'=>'$row > 0'
),
'delete'=>array
(
'imageUrl'=>FALSE,
'label'=>'delete',
'options'=>array('title'=>'delete'),
'click'=>'function(){$("#mydialog").dialog("open"); return false;}',
),
),
'template'=>'{update} | {delete}'
),
Try adding a return to the onclick:
a href="http://example.com" onclick="return confirm("you want to go?")">