JQuery accordion + sortable. Disable parent groups from being draggable - jquery-ui-accordion

I have a list of search options grouped by their section and collapsed using accordion. The user expands a section and drags their selections to the "Saved List". These fields are saved and used for custom columns on tables in my site. Everything is working quite nicely except for a couple minor things.
I want to lock down the parents (Personal, Education, Subject) and only have the li elements inside be draggable. Right now the user can drag the whole group to the top which isn't desirable because I have a limit of fields they can search. Dragging a whole section can exceed that limit and break my tables.
In IE, after collapsing a parent, the li elements "blip" visible for a second before becoming hidden.
Not a huge deal but can be annoying, especially to some of my finicky users :)
Code is below. For styling see the Fiddle of my setup.
I've gotten to the point where I can't see the forests for the trees so if I could just be pointed in the right direction, that would be much appreciated!
HTML:
<div class="demo">
<div id="swaplist" style="height: 0;"></div>
<ul id="sortable2" class='saved'>
<div class="container" style="overflow:auto;">
<div align='center'>
<h2>Saved List</h2>
</div>
<div class="group"></div>
</div>
</ul>
<br />
<ul id="sortable1" class='available'>
<div align='center'>
<h2>Available Fields</h2>
</div>
<br />
<div id="accordion" style="width:950px;">
<div class="group">
<h2><span class="text">Personal</span></h2>
<div class="container">
<ul id="sortable1" class='available' style="width:850px;">
<li class='ui-state-default'><b>First Name</b>
<br />Section: A</li>
<li class='ui-state-default'><b>Last Name</b>
<br />Section: A</li>
<li class='ui-state-default'><b>Date of Birth</b>
<br />Section: A</li>
</ul>
</div>
</div>
<br />
<div class="group">
<h2><span class="text">Education</span></h2>
<div class="container">
<ul id="sortable1" class='available' style="width:850px;">
<li class='ui-state-default'><b>Associate's</b>
<br />Section: B</li>
<li class='ui-state-default'><b>Bachelor's</b>
<br />Section: B</li>
<li class='ui-state-default'><b>Master's</b>
<br />Section: B</li>
<li class='ui-state-default'><b>Doctorate</b>
<br />Section: B</li>
<li class='ui-state-default'><b>Other</b>
<br />Section: B</li>
</ul>
</div>
</div>
<br />
<div class="group">
<h2><span class="text">Subject</span></h2>
<div class="container">
<ul id="sortable1" class='available' style="width:850px;">
<li class='ui-state-default'><b>Science</b>
<br />Section: C</li>
<li class='ui-state-default'><b>Business</b>
<br />Section: C</li>
<li class='ui-state-default'><b>Liberal Arts</b>
<br />Section: C</li>
</ul>
</div>
</div>
<br />
</div>
<!-- End accordion -->
</ul>
<br clear="both" />
</div>
javascript:
$(function () {
$("#accordion")
.accordion({
active: false,
collapsible: true,
animate: false,
heightStyle: "content",
autoHeight: false,
header: "> div > h2"
})
.sortable({
axis: "y",
handle: "h2",
stop: function (event, ui) {
// IE doesn't register the blur when sorting
// so trigger focusout handlers to remove .ui-state-focus
ui.item.children("h2").triggerHandler("focusout");
}
});
$("ul.available").sortable({
connectWith: "ul",
scroll: true,
helper: 'clone', //keeps children visible when pulling out of container
appendTo: '#swaplist' //temporarily stores children in hidden div
});
$("ul.saved").sortable({
connectWith: "ul",
receive: function (event, ui) {
if ($(this).children().length > 9) {
//ui.sender: will cancel the change.
//Useful in the 'receive' callback.
$(ui.sender).sortable('cancel');
}
},
items: "li[id!=nomove]",
update: function () {
var order = $(this).sortable("serialize") + '&action=update';
$.post("ajax_file", order, function (theResponse) {
$("#info").html(theResponse);
});
},
helper: 'clone', //keeps children visible when pulling out of container
appendTo: '#swaplist' //temporarily stores children in hidden div
});
$("#sortable1, #sortable2").disableSelection();
$("#sortable1, #sortable2").disableSelection();
});
UPDATE:
I found out a third thing I needed to fix. I need to make sure the user doesn't select duplicate options. See the answer below for this.

I figured out a way to lock down the parents! It may not be the most ideal way but it works. See the full Fiddle here.
For #1, I applied the answer at How to implement a button on a jQuery Sortable accordion header to lock the parents in place. I made a class called "DontMove" and placed them on <h2> and then set that as my cancel option in every sortable, cancel: ".DontMove". Below is a snippet for implementing it.
$("#accordion")
.accordion({
active: false,
collapsible: true,
animate: false,
heightStyle: "content",
autoHeight: false,
header: "> div > h2"
})
.sortable({
axis: "y",
handle: "h2",
cancel: ".DontMove",
stop: function (event, ui) {
ui.item.children("h2").triggerHandler("focusout");
}
});
For #3 there was a solution thanks to prevent duplicated item in jQueryUI sortable.
This snippet is what did the trick. I placed it in the receive section.
var id = ui.item.attr('id');
var ele = $('#sortable2').find('li[id="'+id+'"]');
if (ele.length != 1) {
$(ui.sender).sortable('cancel');
}
The problem was initially that I couldn't get a list of current elements in the Saved List before the sortable receive function would fire and add the new element. So whenever I would check to see if the added element already existed, it would always cancel since it was already added, thus meeting the condition.
Instead of fighting it, I just got all line elements with the same id. If there were more than 1 then length > 1 so cancel the drop. I know it's not good practice to have duplicate id's but since the lists are database driven there is a chance of that happening and I want to cover all bases.
As far as issue #2, I don't see it happening in Firefox or Chrome so I'm going to leave it alone since I was most worried about #1 and #3.

Related

AngularJS filter is requiring two clicks before it filters results

I am a beginner at angular. I am pretty certain I am doing this the completely incorrect way but because I finally have it "somewhat working" as it works on the second click I am stuck going in this direction and can't seem to figure out another way to do it.
The filter sorts on the second click because it is initialing as "undefined" before the first click and sets it based on that I believe.
In my html:
<div class="col-xs-12 col-sm-4 location-list" ng-repeat="key in careerlist.location">
<div class="locations" ng-click="careerlist.criteriaMatch()">{{key}}
</div>
</div>
<div class="col-xs-12">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-4 job-container" ng-repeat="job in careerlist.career | filter : searchText | filter: selectExperience | filter: careerlist.criteria.name">
<h2>
{{job.title}}
</h2>
<h3>
{{job.location}}
</h3>
<div class="job-description" ng-bind-html="job.description | limitHtml : 200">
</div>
<br><br>
<button>Read More</button>
</div>
<br><br>
</div>
</div>
In my controller:
cl.criteriaMatch = function( criteria ) {
jQuery(document).on('click', '.locations', function(){
cl.criteria = {};
console.log("just the element text " + jQuery(this).text());
cl.criteria.name = jQuery(this).text();
return function( criteria ) {
return criteria.name === criteria.name;
};
});
};
Use ng-click instead of jQuery#on('click'). Angular does not know that the filter should be updated.
Update
#Makoto points out that the click is bound twice. It very much looks like you should just remove the jQuery binding altogether. I would even go so far as suggesting removing jQuery from you project.

How to show tab with carousel avoiding sliding from the current image to the selected image?

In my Angular app I have a simple twitter-bootstrap carousel, in a tab:
<div class="tabbable">
<ul class="nav nav-pills">
<li ng-class="{active: tabSelected === 'main'}">
Main
</li>
<li ng-class="{active: tabSelected === 'photos'}">
Photos
</li>
</ul>
</div>
...
<div class="tab-content" ng-show="tabSelected === 'photos'>
<div class="slides-control">
<carousel disable-animation="false">
<slide ng-repeat="photo in person.photos" active="photo.active">
<img class="slide" ng-src="{{photo.path}}" />
</slide>
</carousel>
</div>
</div>
In the controller I have defined a method, tabSelect(tabName, photoNumber), which allows the tab to be selected in code, and - if the tab is the one named 'photos' - a specific image number can be selected:
<script>
$scope.tabSelect = function (tabName, photoNumber) {
if (tabName === 'photos') {
$scope.person.photos[0].active = false;
$scope.person.photos[photoNumber].active = true;
}
$scope.tabSelected = tabName;
};
</script>
The problem is this:
When calling, for example, $scope.tabSelect('photos', 7);, the photos tab is shown, but initially the previously active image is shown, and then immediately it slides to the selected image (the 7th, in the example). I don't want to avoid using animations, which are quite cool... Though, I want to display immediately the selected image...
I did already try to surround the $scope.tabSelected = tabName; instruction in a $timeout() block, and the selected slide is immediately shown, but not fully rendered (for example, the arrows are not present...).

AngularJS - Show and Hide multiple content

In AngularJS, to simply show a field through an a tag, I would do in this way:
<div ng-show="aField">Content of aField</div>
<a ng-click="aField=true">Show aField</a>
until here, no problem.
I would like now to put more buttons and fields so that, when I click on A it shows the content of A, then when I click on button B, content of A disappears and content of B appears.
How can I do this? Thank you.
UPDATE
Thank you everyone for your solutions, they works! Now, I am doing a template for every content of and because I have much data to show but all in the same structure.
Here the index.html
<div ng-model="methods"
ng-include="'templateMethod.html'"
ng-repeat = "method in methods">
here the script.js:
function Ctrl($scope) {
$scope.methods =
[ { name: 'method1',
description: 'bla bla bla',
benefits: 'benefits of method1',
bestPractices : 'bestPractices',
example: 'example'},
{ name: 'method2',
description: 'bla bla bla',
benefits: 'benefits of method2',
bestPractices : 'bestPractices',
example: 'example'} ];
}
and here the templateMethod.html:
<table>
<tr>
<td>
<div ng-show="toShow=='{{method.name}}Field'">
<h3>{{mmethodethod.name}}</h3>
<p>
<strong>Description</strong>
{{method.description}}
</p>
<p>
<strong>Benefits</strong>
{{method.benefits}}
</p>
<p>
<strong>Best practices</strong>
{{method.bestPractices}}
</p>
<p>
<strong>Examples</strong>
{{method.example}}
</p>
</div>
</td>
<td class = "sidebar">
<ul>
<li><a ng-click="toShow='{{method.name}}Field'" class="{{method.name}} buttons">{{method.name}}</a></li>
</ul>
</td>
</tr>
</table>
It works!
But: if I click the first button and then the second one, the content of the first button do not disappear, it appears under the content of the first button...
Problem with the repetition?
Thanks
It might be better to handle more complex logic in the controller, but in general think about the content of the directive strings as normal js:
<div ng-show="aField">Content of aField</div>
<div ng-show="bField">Content of bField</div>
<a ng-click="aField=true; bField=false">Show aField</a>
<a ng-click="aField=false; bField=true">Show bField</a>
Or use ng-show in concert with ng-hide:
<div ng-show="aField">Content of aField</div>
<div ng-hide="aField">Content of bField</div>
<a ng-click="aField=true">Show aField</a>
<a ng-click="aField=false">Show bField</a>
In the former strategy, nothing shows upon page load. In the latter, the bField content shows by default. If you have more than two items, you might do something like:
<div ng-show="toShow=='aField'">Content of aField</div>
<div ng-show="toShow=='bField'">Content of bField</div>
<div ng-show="toShow=='cField'">Content of cField</div>
<a ng-click="toShow='aField'">Show aField</a>
<a ng-click="toShow='bField'">Show bField</a>
<a ng-click="toShow='cField'">Show cField</a>
I'm guessing that you have a list of items and want to show each item content. Something an accordion component does.
Here is a plunker that shows how you could do it: http://plnkr.co/edit/UTf3dEImiDReC89vULpX?p=preview
Or if you want to display the content on the same place (something like a master detail view) you can do it like this: http://plnkr.co/edit/68DJHL582oY4ecSiiUdE?p=preview
simply use one variable which content is visible. http://jsfiddle.net/gjbw7/
<a ng-click="show='a'">Show aField</a>
.
<div ng-show="show=='a'">Content of aField</div>
I would recommend to create a service in case your fields belong to different controllers.
Service:
App.factory('StateService', function() {
return {
openPanel: ''
};
});
Injecting the service in a Controller:
App.controller('OneCtrl', function($scope, StateService) {
$scope.stateService = StateService;
});
Finally using it a view:
<a ng-click="stateService.openPanel='home'">Home</a>
<div ng-show="stateService.openPanel == 'home'">Content of Home</div>
Demo: http://jsfiddle.net/codef0rmer/BZcdu/
Try this way.
<div>{{content}}</div>
<a ng-click="content='a'">Show aField</a>
<br>
<a ng-click="content='b'">Show bField</a>
<br>
<a ng-click="content='c'">Show cField</a>
<br>
<a ng-click="content='d'">Show dField</a>
<br>
<a ng-click="content='e'">Show eField</a>
<br>
<a ng-click="content='f'">Show fField</a>
Take a look at the ng-switch directive.
<div ng-switch="aField">
<div ng-switch-when="someValue1">
HTML content that will be shown when the aField variable value is equal to someValue1
</div>
<div ng-switch-when="someValue2">
HTML content that will be shown when the aField variable value is equal to someValue2
</div>
<div ng-switch-default>
This is where the default HTML content will go (if aField value is not equal to any of the ng-switch-when values)
</div>
</div>

Can I use the ng-if on a ng-repeater?

I'm new to angular and was wondering how to tackle the following situation. I'm using angular with jquery mobile and am creating a one page app. When the app loads, I have it create pages or divs inside the body tag. I have two types of pages, "typical pages" and "video pages" but I want to use one object to create both page types. I want to use one object because I also use that object to create my menu and I don't want to have to edit multiple objects.
My issue is, I want to create a "typical page" for each element in the object but I only want to create a "video page" for each object that doesn't have "videoPage: false". I tried using ng-if on the "video page" ng-repeater (see html below) but it doesn't seem to be working. Can I use the ng-if on a ng-repeater?
I have the following data/object:
function App($scope){
$scope.pages = [
{
id: "intro",
title: "Intro",
videoPage: false
},{
id: "activeStretches",
title: "Active Stretches by Area"
},{
id: "passiveStretches",
title: "Passive Stretches by Area"
}
];
}
And the html:
<body ng-app ng-controller="App">
<div data-role="page" id="{{page.id}}" ng-repeat="page in pages">
<div data-role="header" data-theme="h">
<a data-rel="back" data-icon="arrow-l">Back</a>
<h1>{{page.title}}</h1>
</div>
<div data-role="content">
<div class="content-side">
<ul data-role="listview" data-inset="true">
<li ng-repeat="menu in pages" data-theme="{{menu.title == page.title && 'h'}}">
{{menu.title}}
</li>
</ul>
<div class="logo"></div>
</div>
<div class="content-main">
Typical Page
</div>
</div>
</div>
<div data-role="page" id="{{page.id}}Video" ng-repeat="page in pages" ng-if="page.videoPage">
<div data-role="header" data-theme="h">
<a data-rel="back" data-icon="arrow-l">Back</a>
<h1 class="vidTitle"></h1>
</div>
<div data-role="content">
<div class="content-side">
<ul data-role="listview" data-inset="true">
<li ng-repeat="menu in pages" data-theme="{{menu.title == page.title && 'h'}}">
{{menu.title}}
</li>
</ul>
<div class="logo"></div>
</div>
<div class="content-main">
Video Page
</div>
</div>
</div>
</body>
ng-if is new as of 1.1.15 so make sure you're using a new (currently unstable) version of Angular.
And, yes, you can mix ng-if with ng-repeat. Here's a fiddle showing it in action (using 1.2): http://jsfiddle.net/rbQry/1/
Note this line (switch the ng-if to "true" and the repeat comes back):
<li ng-repeat="phone in phones | filter:query" ng-if="false">

AngularJS Showing/Hiding content and swapping an image

I have a basic widget block:
<div class="matter ng-controller="ProductsCtrl">
<div class="container-fluid">
<div class="widget">
<div class="widget-head">
<div class="pull-left">Browse live products</div>
<div class="widget-icons pull-right">
<a class="wminimize" href="#" ng-click="showContent = !showContent"><i class="icon-chevron-up"></i></a>
</div>
<div class="clearfix"></div>
</div><!--widget-head-->
<div class="widget-content" ng-class="{ 'hidden': !showContent }">
<div class="padd">
<div>
{{content}}
</div>
</div>
<div class="widget-foot"></div>
</div><!--widget-content-->
</div><!--widget-->
</div>
</div>
And here is the empty controller:
appAdmin.controller("ProductsCtrl", function($scope){
});
I'm using ng-click to hide/show the content, but I am trying to default the content to be visible, and also try to swap the image for the icon chevron down whenever it's hidden. I am very new to Angular and am looking for some direction in adding models and things to make these simple features work.
Any help would be appreciated.
EDIT: Made default visible by switching ng-class="{ 'hidden' : !showContent }" to ng-class="{ 'hidden' : showContent }"
For the content to be visible by default, I would set $scope.showContent to true in the controller. You can use ng-class to change the chevron icon class based on the value of showContent in the following way: <i ng-class="{'icon-chevron-up': showContent, 'icon-chevron-down': !showContent}"></i>. Only the keys of the truthy values will be applied, so it will be either icon-chevron-up or icon-chevron-down. Let me know if I misunderstood your question!

Resources