J tricks - Little JIRA Tricks
  • Home
  • Plugins ↓
    • JQL Tricks Plugin
      • JQL Tricks Plugin - Cloud
        • JQLT Cloud Installation
        • JQLT Cloud Configuration
        • JQLT Cloud Usage
        • JQLT Cloud License
        • JQLT Cloud FAQ
      • JQL Tricks Plugin - DC
        • JQLT DC Installation
        • JQLT DC Configuration
        • JQLT DC Usage
          • JQLT Issue Functions
          • JQLT Subtask Functions
          • JQLT Links Functions
          • JQLT Development Functions
          • JQLT Worklog Functions
          • JQLT Project Functions
          • JQLT Component Functions
          • JQLT Version Functions
          • JQLT Group Functions
          • JQLT User Functions
          • JQLT Date Functions
        • JQLT DC License
        • JQLT DC FAQ
        • JQLT DC Known Issues
        • JQLT DC Performance
      • JQL Tricks Cloud Migration
    • Simplified Planner
      • J-Planner Installation
      • J-Planner Configuration
      • J-Planner Usage
        • Creating a plan
        • Editing a plan
        • Deleting a plan
        • Viewing a plan
        • Modifying a plan
      • J-Planner FAQ
    • Atla-Search Plugin
      • Atla-Search Installation
      • Atla-Search Configuration
      • Atla-Search Usage
      • Atla-Search License
      • Atla-Search FAQ
    • Heroku for Compass App
      • Heroku for Compass Installation
      • Heroku for Compass Configuration
      • Heroku for Compass Usage
    • Datadog for Compass App
      • Datadog for Compass Installation
      • Datadog for Compass Configuration
      • Datadog for Compass Usage
    • Copy to subtask Plugin
    • All Plugins
  • Tutorials
  • The Book
  • Contact Us
  • Home
  • Plugins ↓
    • JQL Tricks Plugin
      • JQL Tricks Plugin - Cloud
        • JQLT Cloud Installation
        • JQLT Cloud Configuration
        • JQLT Cloud Usage
        • JQLT Cloud License
        • JQLT Cloud FAQ
      • JQL Tricks Plugin - DC
        • JQLT DC Installation
        • JQLT DC Configuration
        • JQLT DC Usage
          • JQLT Issue Functions
          • JQLT Subtask Functions
          • JQLT Links Functions
          • JQLT Development Functions
          • JQLT Worklog Functions
          • JQLT Project Functions
          • JQLT Component Functions
          • JQLT Version Functions
          • JQLT Group Functions
          • JQLT User Functions
          • JQLT Date Functions
        • JQLT DC License
        • JQLT DC FAQ
        • JQLT DC Known Issues
        • JQLT DC Performance
      • JQL Tricks Cloud Migration
    • Simplified Planner
      • J-Planner Installation
      • J-Planner Configuration
      • J-Planner Usage
        • Creating a plan
        • Editing a plan
        • Deleting a plan
        • Viewing a plan
        • Modifying a plan
      • J-Planner FAQ
    • Atla-Search Plugin
      • Atla-Search Installation
      • Atla-Search Configuration
      • Atla-Search Usage
      • Atla-Search License
      • Atla-Search FAQ
    • Heroku for Compass App
      • Heroku for Compass Installation
      • Heroku for Compass Configuration
      • Heroku for Compass Usage
    • Datadog for Compass App
      • Datadog for Compass Installation
      • Datadog for Compass Configuration
      • Datadog for Compass Usage
    • Copy to subtask Plugin
    • All Plugins
  • Tutorials
  • The Book
  • Contact Us

One fixVersion please..

12/12/2011

61 Comments

 
Scene 1:

Agile advocate in the room is screaming! How can you have 2 fix versions for a single ticket? How? How? How?

Several scenes (and days) later, Scene N:

Q: We need to restrict the fixVersion(s) field to have only one version. And we need it before the next meeting. Can you?

A: Yup, you are not the first one to ask. Let's do it!


Well, that was the plot! The fixVersion(s) field in JIRA allows multiple versions and this was one of the cases were the customer wanted to restrict the field to have only one version selected.

Doing this was pretty easy in the earlier versions of JIRA where it was a simple multiselect field. All you had to do was to find the appropriate velocity template that is used to render the field and make it a single select instead of multi select. The template was verions-edit.vm under /atlassian-jira/WEB-INF/classes/templates/jira/issue/field and that was it!

In JIRA 4.4.3, things are a bit more tricky. The fixVersion field is rendered using the same template but there is no multi-select field on the screen now. Well, there is one but it is hidden! Instead, the visible field is an Ajax field that populates the options as the user starts typing matching letters. And as the user selects one of the options, the multi select field takes that value. 

You can select more value, ofcourse! And that is what we wanted to restrict. 

The easy choice and perhaps the best (we will see why) is to do a Javascript validation on the submission of the form. Adding Javascript on a field is pretty easy in JIRA. All you need to do is to edit the field's description in the appropriate field configuration and add the Javascript there. This has an added advantage that the Javascript needs to be added only in the field configurations that you want and hence you can limit the restrictions for selected projects, all by simple configuration! That is why I said it is probably the best solution.

And Javascript validation can be done easily using AJS:

  1. Identify the fixVersion field
  2. Find out the nearest form. This is important because we don't want to hard code the create or edit form ids. Instead, this should work wherever the fixVersion field is added!
  3. Add the submit handler for the form and check the number of fixVersions selected
  4. Alert the user if the number of fixVersions is more than one!

Here is what I did:

<script >
  var formId = AJS.$("#fixVersions").closest('form').attr('id');
  AJS.$('#'+formId).submit(function() {
     var count = AJS.$("#fixVersions :selected").length;
     if (count > 1){
        alert('You can select only one Fix Version for an issue');
        return false;
     } else {
        return true;
     }
  });
</script>


And this is what happened when they selected multiple fix versions:
Picture

But, ah the buts, someone asked the question I was trying to avoid. Isn't this a reactive solution? Throwing an error when they submit the form, after they select multiple versions? Can we do something proactive? i.e. just prevent people from selecting it? 

Time to hack the AJS scripts that does the population of options.

Now, if you trace back from the class used in aui params in the versions-edit.vm, aui-field-versionspicker, you will find that initVersionPickers.js file uses it to create the selector picker. And the function used is AJS.MultiSelect. The AJS.MultiSelect function is declared in /atlassian-jira/includes/ajs/select/MultiSelect.js file. And that, is the file where I put my hack!

There might be different places where you can put the hack in but I found it appropriate to place it along side the duplicate check. There is a method _isItemPresent in the MultiSelect.js file which checks whether the option selected is already present or not! I have added few simple lines alongside it to check whether the list has atleast one version selected or not. And if it has, we are going to just alert the user and treat it just like the duplicate scenario. Here is the modified method:

_isItemPresent: function (descriptor) {
        //For fixVersions, return false if more than one version is selected!        
        var elementId  = this.options.element.attr('id');
        if (elementId == 'fixVersions' && this.lozengeGroup.items.length > 0){
        alert('You can select only one Fix Version for an issue');
        return true;
        }
        // Normal duplicate check
        var duplicate = false;
        var value = descriptor.value();
        
        AJS.$.each(this.lozengeGroup.items, function () {
            if (this.value === value) {
                duplicate = true;
                return false; // bail
            }
        });
        return duplicate;
},


And ofcourse, you should check if the field is fixVersions or not because the MutliSelect function is used by other fields like Affected Versions!

And that's it! When you try to select more than one version, user gets the alert then and there. The same alert, but well in advance!

Remember, the change has to be done in the minnified version of the javascript, MultiSelect-min.js as well. And yes, once this is done, it is applied system-wide. i.e. for all the projects. If you need this only for selected projects or you want to remove the checking for selected projects, add extra bits to check for projects. I would try to avoid that and use the first solution in that case.

PS: Don't forget to do this change for every upgrades of JIRA!

PS(2): Like this trick? Have a look at the book to see more!

Edit:  May 25, 2012

What if you are adopting the first method and you want to clear out all the fixVersions after the alert is shown? The following worked in response to Srinivas's query in the comments:

<script >
   var formId = AJS.$("#fixVersions").closest('form').attr('id');

   AJS.$('#'+formId).submit(function() {
       var count = AJS.$("#fixVersions :selected").length;
       if (count > 1){
          alert('You can select only one Fix Version for an issue');
          AJS.$("#fixVersions-multi-select").find('em').click();
          return false;
       } else {
          return true;
       }
   });
</script>


Here the line in bold clears the selected options by triggering a 'click' on the small close icon! - Tested only on 4.4.3.
61 Comments
Matt Doar link
12/12/2011 07:05:23 am

Neat. Other approaches I might try would be the Script Runner with a custom script, or even putting the JavaScript in a plugin to make the next upgrade easier. But that's useful JavaScript up there, thanks!

Reply
J-Tricks
12/12/2011 07:13:52 am

Thanks Matt.

Yes, it will be a good idea to put the script in a plugin of possible. Anything to make upgrades less painful :)

Reply
Matt Doar link
12/12/2011 07:30:45 am

It occurred to me that you might be able to use the change() function on the select element to avoid hacking the MultiSelect.js file.

Using this JavaScript in the Fix Versions description made the alert pop up for me. I'm not sure how to connect that to failing the form submission though, maybe do both?

<script>
AJS.$("select").change(function () {
if (AJS.$('#fixVersions option[selected="selected"]').length > 1) {
alert('You can select only one Fix Version for an issue');
}
});
</script>

Reply
J-Tricks
12/12/2011 08:04:01 am

Yup, The onChange function can alert the user but it isn't preventing the user from adding it. I did try it intially (though the script was slightly different). Maybe use both the alert and the form validation as you suggested.

If there was a way to find out the newly added fixVersion, we could have used jQuery to remove it from the select list!

Reply
mizan
1/19/2012 09:23:10 pm

Hi JTricks,
can we prepopulate Jira fields while creating an issue using the above method ? Can you please provide an example ? I want to prepopulate jira security level field based on user roles/group.

Thanx :)

Reply
J-Tricks
2/5/2012 02:42:34 pm

Mizan,

Here is how I do it. There might be an easy way out there but this one surely is an option ;)

http://www.j-tricks.com/1/post/2012/02/some-ajs-tricks.html

Reply
Kapil
3/12/2012 01:21:36 am

Hello J-Tricks,

Your trick of restricting Single Fixversion version worked for me using the second option of _isItemPresent: function (descriptor) .

Also i would like to restrict AffectsVersion to have a single value,please let me know the required changes to do so.

Thanks
Kapil

Reply
J-tricks
3/12/2012 02:02:06 am

Glad it worked. Use 'versions' instead of 'fixVersions' to make it work for affected versions.

Reply
SRINIVAS
5/25/2012 08:23:24 am

If you are looking for both fix and affects version, this much easier way.

https://confluence.atlassian.com/display/JIRA044/Changing+the+Size+of+the+Fix+Versions+and+Affects+Versions+Select+List

Reply
J-Tricks
5/25/2012 10:59:57 am

Thanks Srini. But it doesn't work in the later versions as it not using tat template anymore.

SRINIVAS
5/25/2012 12:19:53 pm

atlassian-jira-4.4.5-standalone/atlassian-jira/WEB-INF/classes/templates/jira/issue/field/versions-edit.vm is the exact file because i modified it and i am able to see the changes. Though i changed the class of the select box it is still showing the multiselect options. Don't understand why..

J-Tricks
5/25/2012 12:28:48 pm

Yes, exactly. The multi select option is coming from Javascript elsewhere and not from that velocity template. It is the MultiSelect.js file.

SRINIVAS
5/25/2012 01:08:39 pm

I wrote the onchange function and I am able to see the alert.. Can you tell me how to delete the option that is added?

function validateFixVersions(){
var count = AJS.$("#fixVersions :selected").length;
if (count > 1){
alert('You can select only one Fix Version for an issue');

}
}

J-Tricks
5/25/2012 01:48:50 pm

This one will remove all the options selected:

<script type="text/javascript">
var formId = AJS.$("#fixVersions").closest('form').attr('id');

AJS.$('#'+formId).submit(function() {

var count = AJS.$("#fixVersions :selected").length;
if (count > 1){
alert('You can select only one Fix Version for an issue');
AJS.$("#fixVersions-multi-select").find('em').click();
return false;
} else {
return true;
}
});
</script>

Enjoy!

Srinivas
5/25/2012 02:05:28 pm

Thanks Jobin!

This is removing all the selected options. Can we remove only one option from the selected list. Appreciate if you could mention some documentation related to the AJS functions.

J-Tricks
5/25/2012 04:17:34 pm

You won't be able to do that with this approach. For that you will have to adopt the second method mentioned in the tutorial.

SRINIVAS
5/27/2012 09:25:51 am

In some browsers like firefox, the javascript alerts can be controlled by selecting the checkbox (Prevent this page from creating additional dialogs) in the alert. If we select that checkbox, it is not showing any more alerts and there by allowing to select multiple fix versions. Can you please tell me if there is a way to control that?

J-Tricks
5/27/2012 12:06:34 pm

Nope. All JS Hacks are dependent on browsers allowing JS, alerts etc. There is nothing that can be done to override that.

Maybe, instead of an alert, you can add an error message by creating a div on the element and return false after that!

srinivas
5/29/2012 07:46:27 am

The usage of div and span tags creating unnecessary textboxes above the fix version multi select box.

<div id="fix_version_error_message" style="display:none">$textutils.htmlEncode("You can select only one Fix Version for an issue")</div>

How to handle this? Please advise

SRINIVAS
5/29/2012 11:12:36 am

Yes i got it now. It is just a manipulation of div tags. I placed this div tag above the existing ones and finally solved this. Please let me know if there any way to clear only one fix version instead of clearing both.

I followed this approach because i tried to update the multi-select.js but it is not working. No clue about why this is not working though i followed all your steps in the second approach.

J-Tricks
5/29/2012 11:16:10 am

Cool. Regarding the second approach, see this bit in the post:

"Remember, the change has to be done in the minnified version of the javascript, MultiSelect-min.js as well."

Yissar
3/23/2012 12:43:19 am

I am trying to copy the content of the Comments field into my own customfield - Comments1

Can you plesae tell me what is the javascript code for doing this?

Reply
Jorge
5/25/2012 07:58:14 am

Hi there. I love your site and writing style!

I need some help coming up with a very similar JavaScript function that can work for Security Level. I need to enforce a certain security level for certain issue types.

This is what I have so far but it gives me NO errors or desired behavior. I'm even wondering if JavaScript is even running at all in the background.

<script >
var formId = AJS.$("security").closest('form').attr('id');
AJS.$('#'+formId).submit(function() {
var secval = AJS.$("security :selected").value;
if (secval <> 10004){
alert('Please select Security Level -> Other Users for DS Tickets!');
return false;
} else {
return true;
}
});
</script>

I don't mind the "reactive" approach for now because I'm running out of options... once I get the reactive approach nailed, I'll work on being more proactive about the setting of the status :)

Thanks for all your help.

Reply
J-Tricks
5/25/2012 12:13:20 pm

Try this:

<script >
var formId = AJS.$("#security").closest('form').attr('id');
AJS.$('#'+formId).submit(function() {
if (AJS.$("#security").val() != "10004"){
alert('Please select Security Level -> Other Users for DS Tickets!');
return false;
} else {
return true;
}
});
</script>

Reply
Matt Doar link
6/8/2012 09:35:55 am

The irony is that there is a standard custom field type that allows only one Version. My problem is that my client wants a single component.

Reply
J-Tricks
6/8/2012 11:06:49 am

lol. Another hack :)

Reply
Royce
6/15/2012 08:53:35 am

I bought the book. Great stuff!

Questions: I am using JIRA 4.4.5.
How do I set a value in Affect Version/s field? I tried...

1. AJS.$("#versions-textarea").val("12345").click();
"12345" is the version id. This kindda works, but cause "No Matches" to show up initially in the drop-down and the field won't populate with the corresponding version name until I click another field on the screen.

2. AJS.$("#versions-textarea").val("My Version").click();
Using the name of the actual version caused the drop-down list to show with "My Version" highlighted, but not actually populating the field with the value, and clicking away to another field caused the drop-down to disappear and ended up with no value in the field.

In addition, how do I set multiple values in Affect Version/s field?

Thanks!

Reply
Royce
6/20/2012 11:27:02 am

My co-worker suggested:
AJS.$("#versions-textarea").val("1234").blur();
and that worked! So use .blur() instead of .click().

Reply
J-Tricks
6/20/2012 03:17:27 pm

Awesome! Thanks for commenting here. I hope someone will find it useful.

Royce
1/16/2013 10:41:51 am

Well, the .blur() doesn't work in IE 8. :( Someone know a sure way?

Renu
10/17/2012 01:01:04 am

Hi J-Tricks,

I want to make affects version & fix version as a single version picker in Jira 5.1.3.
The above script is not working for Jira 5.1.3. Can you please suggest me an option for the same?

Thanks in advance.
Renu

Reply
J-Tricks
10/17/2012 03:14:32 pm

Which option did you try?

Reply
Renu
10/17/2012 11:37:55 pm

Hi J-Tricks,
Thanks for your reply.

I have modified following files :
1. install_dir>/atlassian-jira/includes/ajs/select/MultiSelect.js
2. <install_dir>/atlassian-jira/includes/ajs/select/MultiSelect-min.js

I have added following script for file MultiSelect.js

_isItemPresent: function (descriptor) {
//For Fix/AffectsVersions, return false if more than one version is selected!
var elementId = this.options.element.attr('id');
if ((elementId == 'versions'|| elementId=='fixVersions') && this.lozengeGroup.items.length > 0)
{
alert('You can select only one Fix/Affects Version for an issue');
return true;
}
// Normal duplicate check
var duplicate = false;
var value = descriptor.value();
AJS.$.each(this.lozengeGroup.items, function () {
if (this.value === value) {
duplicate = true;
return false; // bail
}
});
return duplicate;
},

& for file MultiSelect-min.js following script is added :
_isItemPresent:function(descriptor){var elementId=this.options.element.attr('id');if((elementId =='versions'||elementId=='fixVersions')&&this.lozengeGroup.items.length > 0){ alert('You can select only Fix/Affects Version for an issue');return true;}var duplicate=false;var value=descriptor.value();AJS.$.each(this.lozengeGroup.items,function(){if(this.value === value){duplicate = true;return false;}});return duplicate},

Previously, I was using Jira 4.3.2, In that above scripts are working properly but now I have upgraded my Jira to 5.1.3.

The above scripts are not working properly. I can select two & more afftects & fix versions though the above scripts are added.

Can you please tell me, How can I do this??

Thanks & Regards,
Renu

Nancy Belser
10/30/2012 07:42:22 am

My solution to this problem may not please some of you, but for Jira 4.4.x, I replaced the entire versions-edit.vm with an edited 4.1.1 version (only edit being the removal of multi-select). My users do not get the benefit of the new type ahead control, but it was more important to implement single select. I just tested this with Jira 5.1.6 and it still works. I should note that any version-picker custom field would still use the newer multi-select control. Not sure how to deal with that yet.

Reply
Nancy Belser
10/30/2012 03:14:01 pm

Just read Matt's comment about the single select version custom field. Duh!

Reply
J-Tricks
10/30/2012 03:57:33 pm

One problem solved I guess? lol

Reply
nancy.belser
10/31/2012 01:44:53 am

By the way, I love this site and your book! Thanks for the great work!

J-Tricks
10/31/2012 03:01:48 pm

Thanks. No better feeling than knowing someone finds this useful!

Renu
10/31/2012 01:43:54 am

Hi J-Tricks,

Can you please tell me, How to make affects version & Fix version single version picker in 5.1.3?

Thanks & Regards,
Renu

Reply
Renu
10/31/2012 07:18:40 pm

Hi Jobin,

Sorry to bother you. Single Select affects & fix version scripts are working fine with 5.1.3.

Thanks & Regards,
Renu

Reply
J-Tricks
11/1/2012 01:35:08 am

Oh, that's good to know. I was going to try it out this weekend. Thanks for updating :)

Reply
Srinivas
1/24/2013 09:06:58 am

Hi Renu,

Could you please let me know how you had achieved and have you tried the same in 5.2.2? If possible share the script code please..

Thanks,
Srinivas

Reply
Renu
11/4/2012 05:28:50 pm

Hi Jobin,

Thanks a lot for the useful info.

Reply
Mizan
12/6/2012 10:57:06 pm

Hi Jtricks .

If i Archive a version which was a value in Fix Versions then I get an option to add another Version . How can I prevent this. I cannot have 2 fix versions in any case but for archived version i get an option to add one more version as a fix version ...
Please help

Reply
Mizan
1/6/2013 09:32:58 pm

I found a workaround for this . I hide the complete field if the Archive version is present. Like this my fix version cannot contain 2 versions :)

<script type="text/javascript">
AJS.$(function(){
AJS.$('label').each(function(){
if(AJS.$(this).html() =='Archived Fix Version/s')
{
AJS.$('label[for="fixVersions"]').parent().hide();
}
})
})
</script>

Reply
Srinivas
1/16/2013 09:14:44 am

The below statement seems not working in JIRA 5.2.2.
AJS.$("#fixVersions-multi-select").find('em').click();





Reply
Royce
1/16/2013 10:57:36 am

Ha, probably Atlassian changed the UI/libraries again.

Reply
Srinivas link
1/16/2013 12:52:26 pm

Could you please let me know if there is any other way that can be used to clear the options from the fix versions multiselect component?

ckr
3/28/2013 12:38:35 am

Hi jtricks!!
How can I automatically populate affect versions in sub-task by default from parent in create sub-task screen using javascript?

Reply
Raj
5/23/2013 05:56:28 am

Jobin
above AJS script is not working for Component/s field in JIRA 5.1.8,
do you have simple java script which i can put in description of Component/s field to select only one component?.
Thanks

Reply
Tian
6/26/2013 06:26:09 pm

The script seems not working in 5.2, can you check why. Thanks

Reply
Martin
12/3/2013 05:23:56 am

Hi,
I think I am missing a step for making the change in MultiSelect.js as the change is not being picked up. I have restarted Jira - Is there something else I need to do?

Thanks,

MArtin.

Reply
J-Tricks
12/3/2013 10:18:36 am

Did you change in the minnified version of the javascript, MultiSelect-min.js?

Reply
Martin
12/4/2013 05:23:46 am

Yes - I have just double checked. I am using Jira 5.0.6.

Reply
Lanthanide
3/6/2014 03:22:04 pm

Now, can you do something similar for Labels?

We're getting ready to start using Jira in anger, and are anticipating many users adding labels such as "deg", "degerdation", "DEGRADATION" and "degadation" to issues, when the correct label to use is "degradation".

Is it possible to come up with a white-list of allowable labels, so that if someone has a typo or tries to create an unsanctioned label, they get an error like is shown here?

Reply
J-Tricks
3/6/2014 03:38:40 pm

Frankly, I wouldn't recommend using labels for this purpose. Sounds more like a Select List field (with pre-defined options) but with the Ajax style of picking the values.

We created an "Ajax List" field for one of the customers for the same purpose. You might want to look at a similar solution.

Reply
heshan
10/16/2014 12:36:46 am

Can we disable multi select only for selected projects?

Reply
J-Tricks
10/16/2014 07:35:57 am

This is all javascript. You should be able to add a condition in the script to check for project key.

Reply
Alex Shorkin
9/15/2016 11:02:25 am

What's the current solution of the problem?

Reply
Jira user
4/3/2017 12:08:31 am

Could you please help me with the groovy script which will restrict users to put multiple values in Fix Version field?

J-Trick,
Can I put your 1st script in Behavior plugins in jira 6.4.3?

Reply
Upendra link
4/12/2017 11:00:46 am

Could you please update the code for JIRA version 6.4?

Reply

Your comment will be posted after it is approved.


Leave a Reply.

    Enter your email address:

    Author

    Jobin Kuruvilla - Works in Adaptavist as Head of DevOps Professional Services. 

    Author of JIRA Development Cookbook and JIRA 5.x Development Cookbook.


    RSS Feed

    Categories

    All
    Acive Objects
    Ajs
    Book
    Components
    Condition
    Custom Fields
    Customization
    Events
    Gadgets
    Javascript
    Jql
    Listener
    Mail
    Permissions
    Plugin Framework
    Post Function
    Properties
    Remote Invocation
    Reporting
    Rest
    Scheduled Tasks
    Search
    Services
    Soap
    Summit
    User Interface
    Validator
    Webwork Actions
    Workflow

    Archives

    October 2016
    August 2016
    March 2016
    January 2016
    December 2015
    May 2014
    December 2013
    November 2013
    July 2013
    June 2013
    April 2013
    October 2012
    September 2012
    August 2012
    July 2012
    May 2012
    March 2012
    February 2012
    January 2012
    December 2011
    November 2011
    June 2011
    May 2011
    April 2011
    March 2011
    February 2011
    January 2011
    November 2010
    October 2010
    September 2010
    August 2010

SUPPORT
APPS
TUTORIALS
THE BOOK
© J-Tricks