Hello I have a mobile nav where some items have a subnav so I have set a preventDefault to stop these doing there normal actions and open the sub menu but some don't have a submenu ad now these don't work as normal.
Any help would be great here is my code. Also this is a mobile only problem.
$('.touch .mobile-list > li > a').on('click', function(e){
e.preventDefault();
if($(this).parents('li').hasClass('visible-submenu')) {
$(this).parents('li').removeClass('visible-submenu');
} else {
$('.mobile-list li.visible-submenu').removeClass('visible-submenu');
$(this).parents('li').addClass('visible-submenu');
}
});
Try moving e.preventDefault(); into the appropriate branch of the if statement
for example:
$('.touch .mobile-list > li > a').on('click', function(e){
if($(this).parents('li').hasClass('visible-submenu')) {
$(this).parents('li').removeClass('visible-submenu');
e.preventDefault();
} else {
$('.mobile-list li.visible-submenu').removeClass('visible-submenu');
$(this).parents('li').addClass('visible-submenu');
}
});
Related
I would like a reload.location click-event only if a checkbox is checked. To me this seems to be basic conditions, but it's not working. Perhaps a different approach is needed? What I figured out, is when the checkbox is ticked, there is no html change in the <input type="checkbox"> element. Maybe this is the reason or is the combination of these conditions not possible? The else statement is a call back to the previous UI page. In below attempt, it's skipping the if-statement.
My attempt:
$(document.body).on("click", "#button", function(){
if (document.getElementById('checkbox').checked) {
location.reload(true);
} else {
return_to_page( this.dataset.return )
}
});
Above is working, however it's ignored due to the missing preventDefault:
$(document.body).on("click", "#button", function(e){
e.preventDefault();
//etc
});
checked is not a property of a jQuery object. You can use prop() to get the property instead:
$('#button').click( function() {
if ($('#checkbox').prop('checked')) {
location.reload(true);
}
// alternative #1 - use the 'checked' property of the Element in the jQuery object:
if ($('#checkbox')[0].checked) {
location.reload(true);
}
// alternative #2 - use the 'checked' property of the Element outside of jQuery:
if (document.getElementById('checkbox').checked) {
location.reload(true);
}
});
Here's a working example:
$('#button').click(function() {
if ($('#checkbox').prop('checked')) {
// location.reload(true);
console.log('Reload would happen now...');
} else {
console.log('Staying on the current page');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label>
<input type="checkbox" id="checkbox" />
Reload?
</label>
<button id="button">Go</button>
$('#button').click( function() {
if( $("input[id='checkbox']:checked") ) {
location.reload(true);
}
});
alternative
$('#button').click( function() {
if( $('#checkbox').is(':checked') ) {
location.reload(true);
}
});
All in a one plate methods:
$('#checkbox').is(":checked")
$('#checkbox').prop('checked')
$('#checkbox')[0].checked
$('#checkbox').get(0).checked
I am building a 'Watch this deal' functionality, which is similar to FB 'like' feature. (Ember version 1.13)
Here is the scenario:
There is an icon beside every deal which will enable the current user to 'watch' or 'not watch' the deal. The actions are completed and working and changes on the UI is also working fine. The problem is, when I click on that icon, I become a watcher of the deal but the icon doesn't change. I have to refresh the page to see that change.
controller:
actions:{
// add and remove watchers
addToWatcher: function(deal) {
var _this = this;
var currentUser = this.get('currentUser');
deal.get('watchers').addObject(currentUser);
deal.save().then(function () {
Ember.get(_this, 'flashMessages').success("You are now watching");
}, function() {
// Ember.get(_this, 'flashMessages').danger('apiFailure');
});
},
removeWatcher: function(deal) {
var _this = this;
var currentUser = this.get('currentUser');
deal.get('watchers').removeObject(currentUser);
deal.save().then(function () {
Ember.get(_this, 'flashMessages').success("You are now watching");
}, function() {
// Ember.get(_this, 'flashMessages').danger('apiFailure');
});
}
}
templates:
{{#if (check-watcher deal currentUser.id)}}
<i class="fa fa-2x sc-icon-watch watched" {{action 'removeWatcher' deal}} style="padding: 5px 10px;"></i><br>
{{else}}
<i class="fa fa-2x sc-icon-watch" {{action 'addToWatcher' deal}} style="padding: 5px 10px;"></i><br>
{{/if}}
Here check-watcher is a helper I wrote to check if the deal is being watched by the current user. If it is, the icon will be Red and clicking on it again will trigger 'removeWatcher' action. If not, icon will be black and clicking on it will make user watch the deal.
check-watcher helper:
import Ember from 'ember';
export function checkWatcher(object, currentUser) {
var currentUser = object[1];
var watchers = object[0].get('watchers').getEach('id');
if (watchers.contains(currentUser)) {
return true;
} else{
return false;
}
}
export default Ember.Helper.helper(checkWatcher);
If I were to just change the class, that would have been easy, but I have to change the action too in the views, that's where it's a little tricky.
So, how to make the change in UI happen between adding and removing watchers without refreshing the page?
In short, you need to define a compute method for the helper:
import Ember from 'ember';
export function checkWatcher(object, currentUser) {
var currentUser = object[1];
var watchers = object[0].get('watchers').getEach('id');
if (watchers.contains(currentUser)) {
return true;
} else{
return false;
}
}
export default Ember.Helper.extend({ compute: checkWatcher });
In that case, the helper will recompute its output every time the input changes.
And there is not need to change an action in a template. You could always call 'toggleWatcher' action from template, and then decide what to do in the controller:
toggleWatcher(deal) {
var currentUser = this.get('currentUser');
if (deal.get('watchers').contains(currentUser)) {
this.send('removeWatcher', deal);
} else {
this.send('addToWatcher', deal);
}
}
I'm writing a unit test for a controller that fires up a $modal and uses the promise returned to execute some logic. I can test the parent controller that fires the $modal, but I can't for the life of me figure out how to mock a successful promise.
I've tried a number of ways, including using $q and $scope.$apply() to force the resolution of the promise. However, the closest I've gotten is putting together something similar to the last answer in this SO post;
I've seen this asked a few times with the "old" $dialog modal.
I can't find much on how to do it with the "new" $dialog modal.
Some pointers would be tres appreciated.
To illustrate the problem I'm using the example provided in the UI Bootstrap docs, with some minor edits.
Controllers (Main and Modal)
'use strict';
angular.module('angularUiModalApp')
.controller('MainCtrl', function($scope, $modal, $log) {
$scope.items = ['item1', 'item2', 'item3'];
$scope.open = function() {
$scope.modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
resolve: {
items: function() {
return $scope.items;
}
}
});
$scope.modalInstance.result.then(function(selectedItem) {
$scope.selected = selectedItem;
}, function() {
$log.info('Modal dismissed at: ' + new Date());
});
};
})
.controller('ModalInstanceCtrl', function($scope, $modalInstance, items) {
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function() {
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
});
The view (main.html)
<div ng-controller="MainCtrl">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3>I is a modal!</h3>
</div>
<div class="modal-body">
<ul>
<li ng-repeat="item in items">
<a ng-click="selected.item = item">{{ item }}</a>
</li>
</ul>
Selected: <b>{{ selected.item }}</b>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>
<button class="btn btn-default" ng-click="open()">Open me!</button>
<div ng-show="selected">Selection from a modal: {{ selected }}</div>
</div>
The test
'use strict';
describe('Controller: MainCtrl', function() {
// load the controller's module
beforeEach(module('angularUiModalApp'));
var MainCtrl,
scope;
var fakeModal = {
open: function() {
return {
result: {
then: function(callback) {
callback("item1");
}
}
};
}
};
beforeEach(inject(function($modal) {
spyOn($modal, 'open').andReturn(fakeModal);
}));
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope, _$modal_) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope,
$modal: _$modal_
});
}));
it('should show success when modal login returns success response', function() {
expect(scope.items).toEqual(['item1', 'item2', 'item3']);
// Mock out the modal closing, resolving with a selected item, say 1
scope.open(); // Open the modal
scope.modalInstance.close('item1');
expect(scope.selected).toEqual('item1');
// No dice (scope.selected) is not defined according to Jasmine.
});
});
When you spy on the $modal.open function in the beforeEach,
spyOn($modal, 'open').andReturn(fakeModal);
or
spyOn($modal, 'open').and.returnValue(fakeModal); //For Jasmine 2.0+
you need to return a mock of what $modal.open normally returns, not a mock of $modal, which doesn’t include an open function as you laid out in your fakeModal mock. The fake modal must have a result object that contains a then function to store the callbacks (to be called when the OK or Cancel buttons are clicked on). It also needs a close function (simulating an OK button click on the modal) and a dismiss function (simulating a Cancel button click on the modal). The close and dismiss functions call the necessary call back functions when called.
Change the fakeModal to the following and the unit test will pass:
var fakeModal = {
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;
}
},
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 );
}
};
Additionally, you can test the cancel dialog case by adding a property to test in the cancel handler, in this case $scope.canceled:
$scope.modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$scope.canceled = true; //Mark the modal as canceled
$log.info('Modal dismissed at: ' + new Date());
});
Once the cancel flag is set, the unit test will look something like this:
it("should cancel the dialog when dismiss is called, and $scope.canceled should be true", function () {
expect( scope.canceled ).toBeUndefined();
scope.open(); // Open the modal
scope.modalInstance.dismiss( "cancel" ); //Call dismiss (simulating clicking the cancel button on the modal)
expect( scope.canceled ).toBe( true );
});
To add to Brant's answer, here is a slightly improved mock that will let you handle some other scenarios.
var fakeModal = {
result: {
then: function (confirmCallback, cancelCallback) {
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
return this;
},
catch: function (cancelCallback) {
this.cancelCallback = cancelCallback;
return this;
},
finally: function (finallyCallback) {
this.finallyCallback = finallyCallback;
return this;
}
},
close: function (item) {
this.result.confirmCallBack(item);
},
dismiss: function (item) {
this.result.cancelCallback(item);
},
finally: function () {
this.result.finallyCallback();
}
};
This will allow the mock to handle situations where...
You use the modal with the .then(), .catch() and .finally() handler style instead passing 2 functions (successCallback, errorCallback) to a .then(), for example:
modalInstance
.result
.then(function () {
// close hander
})
.catch(function () {
// dismiss handler
})
.finally(function () {
// finally handler
});
Since modals use promises you should definitely use $q for such things.
Code becomes:
function FakeModal(){
this.resultDeferred = $q.defer();
this.result = this.resultDeferred.promise;
}
FakeModal.prototype.open = function(options){ return this; };
FakeModal.prototype.close = function (item) {
this.resultDeferred.resolve(item);
$rootScope.$apply(); // Propagate promise resolution to 'then' functions using $apply().
};
FakeModal.prototype.dismiss = function (item) {
this.resultDeferred.reject(item);
$rootScope.$apply(); // Propagate promise resolution to 'then' functions using $apply().
};
// ....
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
fakeModal = new FakeModal();
MainCtrl = $controller('MainCtrl', {
$scope: scope,
$modal: fakeModal
});
}));
// ....
it("should cancel the dialog when dismiss is called, and $scope.canceled should be true", function () {
expect( scope.canceled ).toBeUndefined();
fakeModal.dismiss( "cancel" ); //Call dismiss (simulating clicking the cancel button on the modal)
expect( scope.canceled ).toBe( true );
});
Brant's answer was clearly awesome, but this change made it even better for me:
fakeModal =
opened:
then: (openedCallback) ->
openedCallback()
result:
finally: (callback) ->
finallyCallback = callback
then in the test area:
finallyCallback()
expect (thing finally callback does)
.toEqual (what you would expect)
This is the original code to open the opencart minicart with the dopdown arrow
$('#cart > .heading a').live('click', function() {
$('#cart').addClass('active');
$('#cart').live('mouseleave', function() {
$(this).removeClass('active');
$(this).removeAttr('class');
});
});
To close it on desktop screens I only have to leave the dropdown container with the mouse but it doesn't work with a mobile device like iPad or iPhone.
Maybe this could help:
$('#cart > .heading a').live('click', function() {
if($('#cart').hasClass('active') {
$('#cart').removeClass('active');
} else {
$('#cart').addClass('active');
}
$('#cart').live('mouseleave', function() {
$(this).removeClass('active');
$(this).removeAttr('class');
});
});
In shorter version this should also work, but I never found the toggleClass function to work for me, therefore I am toggling manually by if-else as above.
$('#cart > .heading a').live('click', function() {
$('#cart').toggleClass('active');
$('#cart').live('mouseleave', function() {
$(this).removeClass('active');
$(this).removeAttr('class');
});
});
I quite like the filter feature of django admin views (list_filter).
But, on views with a lot of fields, I would really like the ability to minimize/expand it with a click, to save screen real-estate and also because it sometimes actually hides stuff.
Is there an easy way to add a collapse button (some already existing plugin I haven't found or something similar)?
Given that you now have jQuery in django admin, it's easy to bind a slideToggle() to the titles in the List Filter.
This seems enough Javascript for it to work:
// Fancier version https://gist.github.com/985283
;(function($){ $(document).ready(function(){
$('#changelist-filter').children('h3').each(function(){
var $title = $(this);
$title.click(function(){
$title.next().slideToggle();
});
});
});
})(django.jQuery);
Then in the ModelAdmin subclass you want to activate that set the Media inner class:
class MyModelAdmin(admin.ModelAdmin):
list_filter = ['bla', 'bleh']
class Media:
js = ['js/list_filter_collapse.js']
Make sure to drop the list_filter_collapse.js file in a 'js' folder inside your STATIC_DIRS or STATIC_ROOT (Depending on your Django version)
I changed Jj's answer to collapse the whole filter when clicking on the 'filter' title, adding it here for completeness, a gist is available here:
(function($){
ListFilterCollapsePrototype = {
bindToggle: function(){
var that = this;
this.$filterTitle.click(function(){
that.$filterContent.slideToggle();
that.$list.toggleClass('filtered');
});
},
init: function(filterEl) {
this.$filterTitle = $(filterEl).children('h2');
this.$filterContent = $(filterEl).children('h3, ul');
$(this.$filterTitle).css('cursor', 'pointer');
this.$list = $('#changelist');
this.bindToggle();
}
}
function ListFilterCollapse(filterEl) {
this.init(filterEl);
}
ListFilterCollapse.prototype = ListFilterCollapsePrototype;
$(document).ready(function(){
$('#changelist-filter').each(function(){
var collapser = new ListFilterCollapse(this);
});
});
})(django.jQuery);
I have written a small snippets downloadable on bitbucket for the purpose.
The state of the filters are stored in a cookie and the selected filters stay visible.
Thanks to #JJ's idea.
I added toggles for the whole window, simpler than #abyx's implement.
Toggle the whole filter by clicking "Filter" title
Toggle each list by clicking list title
This is the js file content:
;(function($){ $(document).ready(function(){
$('#changelist-filter > h3').each(function(){
var $title = $(this);
$title.click(function(){
$title.next().slideToggle();
});
});
var toggle_flag = true;
$('#changelist-filter > h2').click(function () {
toggle_flag = ! toggle_flag;
$('#changelist-filter > ul').each(function(){
$(this).toggle(toggle_flag);
});
});
});
})(django.jQuery);
Made another change to this so that the H3's are hidden, as well as the filter lists, when you click on the top H2. This will get the entire list of filters out of the way if you click on the top "Filters".
This is the js file content
;(function($){ $(document).ready(function(){
$('#changelist-filter > h3').each(function(){
var $title = $(this);
$title.click(function(){
$title.next().slideToggle();
});
});
var toggle_flag = true;
$('#changelist-filter > h2').click(function () {
toggle_flag = ! toggle_flag;
$('#changelist-filter').find('> ul, > h3').each(function(){
$(this).toggle(toggle_flag);
});
});
});
})(django.jQuery);
Modified fanlix solution to:
Show cursor as pointer on hover
Be folded by default
Code
(function($){ $(document).ready(function(){
$('#changelist-filter > h3').each(function(){
var $title = $(this);
$title.next().toggle();
$title.css("cursor","pointer");
$title.click(function(){
$title.next().slideToggle();
});
});
var toggle_flag = false;
$('#changelist-filter > h2').css("cursor","pointer");
$('#changelist-filter > h2').click(function () {
toggle_flag = ! toggle_flag;
$('#changelist-filter > ul').each(function(){
$(this).slideToggle(toggle_flag);
});
});
});
})(django.jQuery);
Combined Tim's and maGo's approaches, with some tweaks:
Pros:
Allows user to hide the entire list (added "click to hide/unhide" to the filter list title so user knows what to do).
Maintains folded filter categories by default
Cons:
The page refresh after a filter is selected causes the filter categories to fold once again; ideally the ones you're working with would stay open.
The code:
(function($){ $(document).ready(function(){
// Start with a filter list showing only its h3 subtitles; clicking on any
// displays that filter's content; clicking again collapses the list:
$('#changelist-filter > h3').each(function(){
var $title = $(this);
$title.next().toggle();
$title.css("cursor","pointer");
$title.click(function(){
$title.next().slideToggle();
});
});
// Add help after title:
$('#changelist-filter > h2').append("<span style='font-size: 80%; color: grey;'> (click to hide/unhide)</span>");
// Make title clickable to hide entire filter:
var toggle_flag = true;
$('#changelist-filter > h2').click(function () {
toggle_flag = ! toggle_flag;
$('#changelist-filter').find('> h3').each(function(){
$(this).toggle(toggle_flag);
});
});
});
})(django.jQuery);
I wrote a snippets for menu collapse and single element menu collapse.
It's a fork from abyx code, I've just extended it.
If a filter was previously activated the element menu related to this will start as opened.
The filter menu starts closed as default.
Hope this helps
https://github.com/peppelinux/Django-snippets/tree/master/django-admin.js-snippets
Giuseppe De Marco's snippet works best. So i am adding his code snippet here for easy access. It even solves the problem (Cons) discussed above by joelg:
// Copied from
// https://github.com/peppelinux/Django-snippets/tree/master/django-admin.js-snippets
(function($){
var element_2_collapse = '#changelist-filter';
var element_head = 'h2'
var filter_title = 'h3'
// this is needed for full table resize after filter menu collapse
var change_list = '#changelist'
ListFilterCollapsePrototype = {
bindToggle: function(){
var that = this;
this.$filterTitle.click(function(){
// check if some ul is collapsed
// open it before slidetoggle all together
$(element_2_collapse).children('ul').each(function(){
if($(this).is(":hidden"))
{
$(this).slideToggle();
}
})
// and now slidetoggle all
that.$filterContentTitle.slideToggle();
that.$filterContentElements.slideToggle();
that.$list.toggleClass('filtered');
});
},
init: function(filterEl) {
this.$filterTitle = $(filterEl).children(element_head);
this.$filterContentTitle = $(filterEl).children(filter_title);
this.$filterContentElements = $(filterEl).children('ul');
$(this.$filterTitle).css('cursor', 'pointer');
this.$list = $(change_list );
// header collapse
this.bindToggle();
// collapsable childrens
$(element_2_collapse).children(filter_title).each(function(){
var $title = $(this);
$title.click(function(){
$title.next().slideToggle();
});
$title.css('border-bottom', '1px solid grey');
$title.css('padding-bottom', '5px');
$title.css('cursor', 'pointer');
});
}
}
function ListFilterCollapse(filterEl) {
this.init(filterEl);
}
ListFilterCollapse.prototype = ListFilterCollapsePrototype;
$(document).ready(function(){
$(element_2_collapse).each(function(){
var collapser = new ListFilterCollapse(this);
});
// close them by default
$(element_2_collapse+' '+element_head).click()
// if some filter was clicked it will be visible for first run only
// selezione diverse da Default
$(element_2_collapse).children(filter_title).each(function(){
lis = $(this).next().children('li')
lis.each(function(cnt) {
if (cnt > 0)
{
if ($(this).hasClass('selected')) {
$(this).parent().slideDown();
$(this).parent().prev().slideDown();
// if some filters is active every filters title (h3)
// should be visible
$(element_2_collapse).children(filter_title).each(function(){
$(this).slideDown();
})
$(change_list).toggleClass('filtered');
}
}
})
});
});
})(django.jQuery);
Django 4.x, here is how I do.
create admin template as below
{% extends "admin/change_list.html" %} {% block extrastyle %}
{{ block.super }}
function toggle_filter() {
$("#changelist-filter").toggle("slow");
};
$(document).ready(function(){
// close them by default
$("#changelist-filter").toggle("fast");
}); {% endblock %}
enhance admin/base_site.html to add button
<button onclick="toggle_filter()" class="btn btn-warning btn-sm" type="submit">Toggle Filter</button>