1

I have a question about combining Twig and jQuery in Craft. I have a couple of filters on a page that outputs some entries: http://www.joycefdn.org/grants

My categories are organized like this: Education and Economic Mobility - Educator Quality - Innovation
- Pathways Environment - Climate Solutions
- Great Lakes
Culture - Arts Access and Participation
- Arts Leadership and Workforce
- Creativity and Cultural Production
- Joyce Awards Democracy
- Fair Elections
- Media
Gun Violence Prevention and Justice Reform
- Gun Violence Prevention
- 21st Century Policing
- Reducing Mass Incarceration
Special Opportunities
- Communications
- Cross-Programmatic Innovations
- Digital Tools

with the top level being Programs and the second level being focus areas. I would like to have it so that when a Program is selected then the Focus Area select menu gets populated with the categories that are children of that program. I currently have this jQuery left over from another method I tried which does get the current Programs that are selected:

$(".grants-data form[name='grantSearch'] select[name='program']").change(function(){
    var selectedPrograms = $("form[name='grantSearch'] input[data-name='selectItemprogram'][value!='']:checked").map(function() {
    return this.value;
                }).get().join('|');
});

and I have this which appends all of the descendants of all categories to the focus area menu:

{% set catSlug = craft.categories.title('focusArea').first() %}
            {% set childFAs = craft.categories.descendantOf(catSlug) %} 
            $("select#focusArea").html('<option value="">Focus Area</option>');
            {% for fa in childFAs %}
            $("select#focusArea").append('<option value="{{fa.title}}">{{fa.title}}</option>');
            {% endfor %}
            {% if faSlug|length %}
                $("select#focusArea option[value='{{faSlugOrig}}']").attr('selected',true);
            {% endif %}
            </script>

but I can't seem to connect these two so that only the descendants of the program that is clicked get populated. I think I may be overthinking this or trying to combine two methods that just won't combine. Any help would be greatly appreciated!

Thanks!!

  • Would an answer without jquery be ok for you too? I don't like jquery for this task but I could help you – Robin Schambach Dec 8 '17 at 23:59
  • Sure! Any answer would be okay with me :-) – Elisa C Dec 9 '17 at 0:07
0

Okay, please don't be afraid and give it a try. I'm using the angular way, it seems a little bit difficult but it's actually really easy once you get used to it and works perfectly as soon as you understand it a little bit. If you need changes I'll provide whatever you need.
Edit: here you can see a live example for it plunker or course without css but it is your html

Let's get into it. For testing purposes I created this category structure in my backend and called the group filter. enter image description here

First you need to include this library you can of course use the {% includeJsRessource %} as well

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.min.js"></script>

Then you create 2 javascript arrays one for the Culture, Democracy... I call them parents and one for their children that belong to them

// get all filters of the first level
{% set filters = craft.categories.group('filter').level(1).find() %}
<script type="text/javascript">
    var children = [];
    var parents = [];
    {% for filter in filters %}
        parents.push({
            title: "{{ filter.title }}",
            id: "{{ filter.id }}"
        });
        // now fetch all children for them
        {% set children = craft.categories.group('filter').descendantOf(filter).level(2).find() %}
        {% for child in children %}
            children.push({
                title: "{{ child.title }}",
                id: "{{ child.id }}",
                parent: "{{ filter.id }}"
            });
        {% endfor %}
    {% endfor %}
</script>

In the end you'll have 2 array that could look likeenter image description here

Now the funny part starts include this javascript file. You can ask whatever you want I'll explain it. Even if the syntax and everything what's going on seems a little bit strange, but make sure this is loaded after the library from above

var  myApp = angular.module('myApp',[]).config(function($interpolateProvider) {
    $interpolateProvider.startSymbol('{[{').endSymbol('}]}');
});

myApp.controller('filterController', ['$scope', function ($scope) {
    // all parents
    $scope.parents = parents;
    // all children
    $scope.children = children;

    // return if the parent is selected or not
    $scope.filterBySelectedParent = function (item) {
        return $scope.selectedParents.indexOf(item.parent) !== -1;
    };

    // an array with all selected parent ids
    $scope.selectedParents = [];

    // include an item to the array or slice it when the input it clicked
    $scope.addFilter = function(id){
        var index = $scope.selectedParents.indexOf(id);
        if(index === -1){
            $scope.selectedParents.push(id);
        }else{
            $scope.selectedParents.splice(index, 1);
        }
    }
}]);

And finally your new html

<!-- this is your menu or your filter bar or whatever you want to call it -->
<!-- I inspected your html and tried to use that but you must include ng-app and ng-controller somewhere -->
<div ng-app="myApp" ng-controller="filterController">
    <!-- your first ul... all your parents, the first filter in your page -->
    <ul>
        <li ng-repeat="filter in parents">
            <label class="">
                <input type="checkbox" ng-click="addFilter(filter.id);" value="{[{filter.id}]}"><span>{[{ filter.title }]}</span>
            </label>
        </li>
    </ul>

    <!-- all your children, the second filter in your page -->
    <ul>
        <li class="" ng-repeat="child in children | filter: filterBySelectedParent">
            <label class="" >
                <input type="checkbox" data-name="selectItemfocusArea" value="{[{ child.id }]}"><span>{[{ child.title }]}</span>
            </label>
        </li>
    </ul>

    <!-- include the rest here -->
</div>

Explanation: so what actually happens is the following

  1. you create 2 arrays in javascript based on your backend inputs
  2. angular renders your menu based on those two arrays but the second one has a filter. Only clicked items of the first array are shown
  3. as soon as someone clicks on an filter it is included in an array $scope.selectedParents
  4. any change in this array is recognized automatically which results, that the html renders again => your other filters are shown.

Please tell me if I missunderstood you somehow.. based on my current state I can change nearly everything really easily in just a few clicks. For example if you would like to automatically select your children you would include ng-checked="filterBySelectedParent(child)" on your input and remove the | filter: filterBySelectedParent.. that's it

  • Thanks for this solution! As for the HTML, it's actually possible to just add the options to the select box and it gets generated into that ul/li list with labels with a plugin I'm using to create the multi-checkbox functionality. I will try out your method though and let you know how it goes! – Elisa C Dec 9 '17 at 2:08
  • Ah okay sry I thought it was your work. My solution has definitely disadvantages, for example you have to get used to the new methods but from my experience (and everyone else will say the same who uses angular) for searching/filtering and filter that depends other filters and all these kind of things this method is perfect because you don't have to care about the html since angular does everything for you and of course it boosts your performance compared to php or jquery. Anyway I wish you good luck – Robin Schambach Dec 9 '17 at 2:17
  • Hi Robin, thanks for much for the help. I've made a test page to test your solution and it's not quite working: joycefdn.org/grantsDB I tried to convert the HTML to the select boxes that I need and it doesn't work when I apply the multiple select option plugin to the selects, in that case, the secondary select box does not fill in at all. Also, I need the values of the select boxes to still be the titles of the categories because that's how the filtering works. Any help would be greatly appreciated!! – Elisa C Dec 13 '17 at 2:37
  • This is how I tried to edit it: <div ng-app="myApp" ng-controller="filterController"> <select id="program" name="program" multiple="multiple"> <option value="" selected>Program</option> <option ng-repeat="filter in parents" ng-click="addFilter(filter.id);" value="{[{filter.id}]}">{[{ filter.title }]}</option> </select> <select id="focusArea" name="focusArea" multiple="multiple"> <option class="" ng-repeat="child in children | filter: filterBySelectedParent"><span data-name="selectItemfocusArea" value="{[{ child.id }]}">{[{ child.title }]}</span> </option> </select> </div> – Elisa C Dec 13 '17 at 2:41
  • I'm so sorry, I'm really new to Stack Exchange, I think I forgot to tag you... @robin-schambach – Elisa C Dec 13 '17 at 3:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.