소스 검색

HUE-1917 [oozie] Crontab support in dashboard

Extended jqCron to support readonly too
Added jqCron to coordinator list and coordinator details
Enrico Berti 11 년 전
부모
커밋
7a8d897

+ 13 - 0
apps/oozie/src/oozie/templates/dashboard/list_oozie_coordinator.mako

@@ -56,7 +56,11 @@ ${ layout.menubar(section='coordinators', dashboard=True) }
         </li>
 
         <li class="nav-header">${ _('Frequency') }</li>
+        % if enable_cron_scheduling:
+        <li class="white cron-frequency"><input class="value" type="hidden" value="${ oozie_coordinator.frequency }"/></li>
+        % else:
         <li class="white">${ oozie_coordinator.frequency } ${ oozie_coordinator.timeUnit }</li>
+        % endif
 
         <li class="nav-header">${ _('Next Materialized Time') }</li>
         <li class="white" id="nextTime">${ utils.format_time(oozie_coordinator.nextMaterializedTime) }</li>
@@ -357,6 +361,10 @@ ${ layout.menubar(section='coordinators', dashboard=True) }
 <script src="/static/js/jquery.blueprint.js"></script>
 % endif
 
+% if enable_cron_scheduling:
+<link href="/static/css/jqCron.css" rel="stylesheet" type="text/css" />
+<script src="/static/js/jqCron.js" type="text/javascript"></script>
+% endif
 
 <style type="text/css">
   .CodeMirror.cm-s-default {
@@ -433,6 +441,11 @@ ${ layout.menubar(section='coordinators', dashboard=True) }
 
     $("*[rel=tooltip]").tooltip();
 
+    % if enable_cron_scheduling:
+      ${ utils.cron_js() }
+      renderCrons();
+    % endif
+
     $(".dataset").each(function () {
       if ($(this).text().length > 15) {
         $(this).html($(this).text().substr(0, 14) + "&hellip;");

+ 36 - 1
apps/oozie/src/oozie/templates/dashboard/list_oozie_coordinators.mako

@@ -63,8 +63,12 @@ ${layout.menubar(section='coordinators', dashboard=True)}
           <th width="20%">${ _('Name') }</th>
           <th width="5%">${ _('Progress') }</th>
           <th width="10%">${ _('Submitter') }</th>
+          % if enable_cron_scheduling:
+          <th width="8%">${ _('Frequency') }</th>
+          % else:
           <th width="3%">${ _('Frequency') }</th>
           <th width="5%">${ _('Time Unit') }</th>
+          % endif
           <th width="12%">${ _('Start Time') }</th>
           <th width="15%">${ _('Id') }</th>
           <th width="10%">${ _('Action') }</th>
@@ -86,8 +90,12 @@ ${layout.menubar(section='coordinators', dashboard=True)}
           <th width="20%">${ _('Name') }</th>
           <th width="10%">${ _('Duration') }</th>
           <th width="10%">${ _('Submitter') }</th>
+          % if enable_cron_scheduling:
+          <th width="10%">${ _('Frequency') }</th>
+          % else:
           <th width="5%">${ _('Frequency') }</th>
           <th width="5%">${ _('Time Unit') }</th>
+          % endif
           <th width="13%">${ _('Start Time') }</th>
           <th width="20%">${ _('Id') }</th>
         </tr>
@@ -117,6 +125,11 @@ ${layout.menubar(section='coordinators', dashboard=True)}
 <script src="/oozie/static/js/bundles.utils.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
 
+% if enable_cron_scheduling:
+<link href="/static/css/jqCron.css" rel="stylesheet" type="text/css" />
+<script src="/static/js/jqCron.js" type="text/javascript"></script>
+% endif
+
 <script type="text/javascript" charset="utf-8">
 
   var Coordinator = function (c) {
@@ -156,7 +169,9 @@ ${layout.menubar(section='coordinators', dashboard=True)}
         null,
         null,
         null,
+        % if not enable_cron_scheduling:
         null,
+        % endif
         null,
         { "bSortable":false }
       ],
@@ -194,7 +209,9 @@ ${layout.menubar(section='coordinators', dashboard=True)}
         null,
         null,
         null,
+        % if not enable_cron_scheduling:
         null,
+        % endif
         null
       ],
       "aaSorting":[
@@ -314,6 +331,11 @@ ${layout.menubar(section='coordinators', dashboard=True)}
       });
     });
 
+    % if enable_cron_scheduling:
+    ${ utils.cron_js() }
+    % endif
+
+
     refreshRunning();
     refreshCompleted();
 
@@ -384,8 +406,12 @@ ${layout.menubar(section='coordinators', dashboard=True)}
                     coord.appName,
                     '<div class="progress"><div class="' + coord.progressClass + '" style="width:' + coord.progress + '%">' + coord.progress + '%</div></div>',
                     coord.user,
+                    % if enable_cron_scheduling:
+                    '<div class="cron-frequency"><input class="value" type="hidden" value="'+emptyStringIfNull(coord.frequency)+'"/></div>',
+                    % else:
                     emptyStringIfNull(coord.frequency),
                     emptyStringIfNull(coord.timeUnit),
+                    % endif
                     emptyStringIfNull(coord.startTime),
                     '<a href="' + coord.absoluteUrl + '" data-row-selector="true">' + coord.id + '</a>',
                     killCell + " " + (['RUNNING', 'PREP', 'WAITING'].indexOf(coord.status) > -1?suspendCell:resumeCell)
@@ -411,7 +437,9 @@ ${layout.menubar(section='coordinators', dashboard=True)}
           refreshCompleted();
         }
         numRunning = data.length;
-
+        % if enable_cron_scheduling:
+        renderCrons(); // utils.inc.mako
+        % endif
         window.setTimeout(refreshRunning, 20000);
       });
     }
@@ -428,8 +456,12 @@ ${layout.menubar(section='coordinators', dashboard=True)}
               coord.appName,
               emptyStringIfNull(coord.duration),
               coord.user,
+              % if enable_cron_scheduling:
+              '<div class="cron-frequency"><input class="value" type="hidden" value="'+emptyStringIfNull(coord.frequency)+'"/></div>',
+              % else:
               emptyStringIfNull(coord.frequency),
               emptyStringIfNull(coord.timeUnit),
+              % endif
               emptyStringIfNull(coord.startTime),
               '<a href="' + coord.absoluteUrl + '" data-row-selector="true">' + coord.id + '</a>'
             ], false);
@@ -439,6 +471,9 @@ ${layout.menubar(section='coordinators', dashboard=True)}
           }
         });
         completedTable.fnDraw();
+        % if enable_cron_scheduling:
+        renderCrons(); // utils.inc.mako
+        % endif
       });
     }
   });

+ 8 - 10
apps/oozie/src/oozie/templates/editor/coordinator_utils.mako

@@ -21,19 +21,17 @@
 
 
 <%def name="frequency_fields()">
-  <div class="control-group" rel="popover" >
+  <div class="control-group">
     <label class="control-label">${ _('When') }</label>
     <div class="controls">
       <div class="row-fluid">
-        <div class="span9">
-          <a data-bind="visible: isAdvancedCron" href="http://quartz-scheduler.org/api/2.0.0/org/quartz/CronExpression.html" class="pull-right" target="_blank">&nbsp;<i class="fa fa-question-circle" title="${ _('Check syntax ?') }"></i></a>
-          <input data-bind="visible: isAdvancedCron" id="coord-frequency" name="cron_frequency" class="pull-right"/>
-          <span data-bind="visible: isAdvancedCron" class="pull-right" style="padding-right:20px">
-            ${ _('Crontab') }
-          </span>&nbsp;
-          <input type="checkbox" name="isAdvancedCron" data-bind="checked: isAdvancedCron" /> (${ _('advanced') })
-        </div>
-        <div class="span3">
+        <div class="span12">
+          <div style="display: inline-block; min-height: 29px; line-height: 29px">
+            <span data-bind="visible: isAdvancedCron">${ _('Crontab') }</span>
+            <a data-bind="visible: isAdvancedCron" href="http://quartz-scheduler.org/api/2.0.0/org/quartz/CronExpression.html" target="_blank" title="${ _('View syntax rules') }" rel="tooltip"><i class="fa fa-question-circle"></i></a>
+            <input type="text" data-bind="visible: isAdvancedCron" id="coord-frequency" name="cron_frequency" style="margin-right: 20px"/>
+          </div>
+          <label class="checkbox" style="display: inline-block"><input type="checkbox" name="isAdvancedCron" data-bind="checked: isAdvancedCron" /> (${ _('advanced') })</label>
         </div>
       </div>
       <span class="help-block">${ _('The time frequency at which interval a workflow should be submitted by the coordinator. For example, once a day, every Monday at midnight, every week days, every three days...') }</span>

+ 6 - 3
apps/oozie/src/oozie/templates/editor/create_coordinator.mako

@@ -133,8 +133,10 @@ ${ layout.menubar(section='coordinators') }
   </div>
 </div>
 
-<link rel="stylesheet" type="text/css" href="/static/ext/jqCron/jqCron.css" />
-<script type="text/javascript" src="/static/ext/jqCron/jqCron.min.js"></script>
+% if enable_cron_scheduling:
+<link href="/static/css/jqCron.css" rel="stylesheet" type="text/css" />
+<script src="/static/js/jqCron.js" type="text/javascript"></script>
+% endif
 
 <script type="text/javascript" src="/oozie/static/js/coordinator.js"></script>
 
@@ -143,7 +145,8 @@ ${ layout.menubar(section='coordinators') }
   $(document).ready(function () {
 
     % if enable_cron_scheduling:
-      initCoordinator(${ coordinator_frequency | n,unicode });
+      ${ utils.cron_js() }
+      initCoordinator(${ coordinator_frequency | n,unicode }, cron_i18n); // cron_i18n comes from utils.inc.mako
     % endif
 
     var currentStep = "step1";

+ 6 - 3
apps/oozie/src/oozie/templates/editor/edit_coordinator.mako

@@ -453,8 +453,10 @@ ${ layout.menubar(section='coordinators') }
 <form class="form-horizontal" id="add-dataset-form"></form>
 <form class="form-horizontal" id="edit-dataset-form"></form>
 
-<link rel="stylesheet" type="text/css" href="/static/ext/jqCron/jqCron.css" />
-<script type="text/javascript" src="/static/ext/jqCron/jqCron.min.js"></script>
+% if enable_cron_scheduling:
+<link href="/static/css/jqCron.css" rel="stylesheet" type="text/css" />
+<script src="/static/js/jqCron.js" type="text/javascript"></script>
+% endif
 
 <script type="text/javascript" src="/oozie/static/js/coordinator.js"></script>
 
@@ -623,7 +625,8 @@ ${ layout.menubar(section='coordinators') }
       };
 
       % if enable_cron_scheduling:
-        initCoordinator(${ coordinator_frequency | n,unicode });
+        ${ utils.cron_js() }
+        initCoordinator(${ coordinator_frequency | n,unicode }, cron_i18n); // cron_i18n comes from utils.inc.mako
       % endif
 
       window.slaModel = new slaModel();

+ 19 - 0
apps/oozie/src/oozie/templates/editor/list_coordinators.mako

@@ -30,6 +30,9 @@ ${ layout.menubar(section='coordinators') }
   input.search-query {
     vertical-align: top;
   }
+  .cron-frequency {
+    white-space: nowrap;
+  }
 </style>
 
 <div class="container-fluid">
@@ -103,7 +106,11 @@ ${ layout.menubar(section='coordinators') }
               ${ coordinator.workflow }
             % endif
           </td>
+          % if enable_cron_scheduling:
+          <td class="cron-frequency"><input class="value" type="hidden" value="${ coordinator.cron_frequency["frequency"] }"/></td>
+          % else:
           <td>${ coordinator.text_frequency }</td>
+          % endif
           <td>
             <span class="label label-info">${ coordinator.status }</span>
           </td>
@@ -162,6 +169,12 @@ ${ layout.menubar(section='coordinators') }
 <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
 
+% if enable_cron_scheduling:
+<link href="/static/css/jqCron.css" rel="stylesheet" type="text/css" />
+<script src="/static/js/jqCron.js" type="text/javascript"></script>
+% endif
+
+
 <script type="text/javascript" charset="utf-8">
   $(document).ready(function () {
     var viewModel = {
@@ -310,6 +323,12 @@ ${ layout.menubar(section='coordinators') }
     });
 
     $("a[data-row-selector='true']").jHueRowSelector();
+
+    % if enable_cron_scheduling:
+    ${ utils.cron_js() }
+    renderCrons(); // from utils.inc.mako
+    % endif
+
   });
 </script>
 

+ 48 - 1
apps/oozie/src/oozie/templates/utils.inc.mako

@@ -279,7 +279,6 @@
   </div>
 </%def>
 
-
 ## Would be nice include it in slaForm() somehow
 <%def name="slaGlobal()">
   function addSLATextAndPlaceholder(elements, $data) {
@@ -523,3 +522,51 @@
   </script>
 
 </%def>
+
+<%def name="cron_js()">
+var cron_i18n = {
+    empty: '${_('-all-')}',
+    name_minute: '${_('minute')}',
+    name_hour: '${_('hour')}',
+    name_day: '${_('day')}',
+    name_week: '${_('week')}',
+    name_month: '${_('month')}',
+    name_year: '${_('year')}',
+    text_period: '${_('Every')} <b />',
+    text_mins: ' ${_('at')} <b /> ${_('minutes past the hour')}',
+    text_time: ' ${_('at')} <b />:<b />',
+    text_dow: ' ${_('on')} <b />',
+    text_month: ' ${_('of')} <b />',
+    text_dom: ' ${_('on the')} <b />',
+    error1: '${_('The tag %s is not supported !')}',
+    error2: '${_('Bad number of elements')}',
+    error3: '${_('The jquery_element should be set into jqCron settings')}',
+    error4: '${_('Unrecognized expression')}',
+    weekdays: ['${_('monday')}', '${_('tuesday')}', '${_('wednesday')}', '${_('thursday')}', '${_('friday')}', '${_('saturday')}', '${_('sunday')}'],
+    months: ['${_('january')}', '${_('february')}', '${_('march')}', '${_('april')}', '${_('may')}', '${_('june')}', '${_('july')}', '${_('august')}', '${_('september')}', '${_('october')}', '${_('november')}', '${_('december')}']
+}
+function renderCrons() {
+    $(".cron-frequency").each(function(){
+      var _val = $(this).find(".value");
+      _val.jqCron({
+        texts: {
+          i18n: cron_i18n // comes from utils.inc.mako
+        },
+        readonly: true,
+        enabled_minute: false,
+        multiple_dom: true,
+        multiple_month: true,
+        multiple_mins: true,
+        multiple_dow: true,
+        multiple_time_hours: true,
+        multiple_time_minutes: false,
+        default_period: 'day',
+        default_value: _val.val(),
+        no_reset_button: true,
+        lang: 'i18n'
+      })
+      .jqCronGetInstance();
+    });
+}
+</%def>
+

+ 7 - 2
apps/oozie/src/oozie/views/dashboard.py

@@ -38,7 +38,7 @@ from desktop.log.access import access_warn
 from liboozie.oozie_api import get_oozie
 from liboozie.submittion import Submission
 
-from oozie.conf import OOZIE_JOBS_COUNT
+from oozie.conf import OOZIE_JOBS_COUNT, ENABLE_CRON_SCHEDULING
 from oozie.forms import RerunForm, ParameterForm, RerunCoordForm,\
   RerunBundleForm
 from oozie.models import History, Job, Workflow, utc_datetime_format, Bundle,\
@@ -125,6 +125,7 @@ def list_oozie_coordinators(request):
     kwargs['user'] = request.user.username
 
   coordinators = get_oozie(request.user).get_coordinators(**kwargs)
+  enable_cron_scheduling = ENABLE_CRON_SCHEDULING.get()
 
   if request.GET.get('format') == 'json':
     json_jobs = coordinators.jobs
@@ -137,6 +138,7 @@ def list_oozie_coordinators(request):
   return render('dashboard/list_oozie_coordinators.mako', request, {
     'jobs': split_oozie_jobs(request.user, coordinators.jobs),
     'has_job_edition_permission': has_job_edition_permission,
+    'enable_cron_scheduling': enable_cron_scheduling,
   })
 
 
@@ -275,6 +277,8 @@ def list_oozie_coordinator(request, job_id):
     }
     oozie_slas = api.get_oozie_slas(**params)
 
+  enable_cron_scheduling = ENABLE_CRON_SCHEDULING.get()
+
   return render('dashboard/list_oozie_coordinator.mako', request, {
     'oozie_coordinator': oozie_coordinator,
     'oozie_slas': oozie_slas,
@@ -282,7 +286,8 @@ def list_oozie_coordinator(request, job_id):
     'oozie_bundle': oozie_bundle,
     'has_job_edition_permission': has_job_edition_permission,
     'show_all_actions': show_all_actions,
-    'MAX_COORD_ACTIONS': MAX_COORD_ACTIONS
+    'MAX_COORD_ACTIONS': MAX_COORD_ACTIONS,
+    'enable_cron_scheduling': enable_cron_scheduling,
   })
 
 

+ 3 - 0
apps/oozie/src/oozie/views/editor.py

@@ -86,9 +86,12 @@ def list_coordinators(request, workflow_id=None):
   if workflow_id is not None:
     data = [job for job in data if job.workflow.id == workflow_id]
 
+  enable_cron_scheduling = ENABLE_CRON_SCHEDULING.get()
+
   return render('editor/list_coordinators.mako', request, {
     'jobs': data,
     'json_jobs': json.dumps([job.id for job in data]),
+    'enable_cron_scheduling': enable_cron_scheduling,
   })
 
 

+ 5 - 2
apps/oozie/static/js/coordinator.js

@@ -16,11 +16,14 @@
 
 // Not in coordinator_properties as also used in create coordinator.
 
-function initCoordinator(coordinator_json) {
+function initCoordinator(coordinator_json, i18n) {
 
   var coordCron =
     $('#coord-frequency')
       .jqCron({
+        texts: {
+          i18n: i18n
+        },
         enabled_minute: false,
         multiple_dom: true,
         multiple_month: true,
@@ -31,7 +34,7 @@ function initCoordinator(coordinator_json) {
         default_period: 'day',
         default_value: coordinator_json.frequency,
         no_reset_button: true,
-        lang: 'en'
+        lang: 'i18n'
       })
       .jqCronGetInstance();
 

+ 19 - 6
desktop/core/static/ext/jqCron/jqCron.css → desktop/core/static/css/jqCron.css

@@ -14,37 +14,49 @@
 .jqCron-cross,
 .jqCron-selector-title {
 	cursor: pointer;
-	border-radius: 3px;
+	border-radius: 2px;
 	border: 1px solid #ddd;
 	margin: 0 0.2em;
-	padding: 0 0.5em;
+	padding: 0.2em 0.5em;
 }
 .jqCron-container.disable .jqCron-cross:hover,
 .jqCron-container.disable .jqCron-selector-title:hover,
 .jqCron-cross,
 .jqCron-selector-title {
-	background: #eee;
+	background: #fff;
 	border-color: #ddd;
 }
 .jqCron-cross:hover,
 .jqCron-selector-title:hover {
-	background-color: #ddd;
+	background-color: #f3f3f3;
 	border-color: #aaa;
 }
+
+.jqCron-container.readonly .jqCron-cross,
+.jqCron-container.readonly .jqCron-cross:hover,
+.jqCron-container.readonly .jqCron-selector-title,
+.jqCron-container.readonly .jqCron-selector-title:hover {
+  border: none;
+  font-weight: bold;
+  background-color: transparent;
+  padding: 0;
+  margin: 0;
+}
+
 .jqCron-cross {
 	border-radius: 1em;
 	font-size: 80%;
 	padding: 0 0.3em;
 }
 .jqCron-selector-list {
-	background: #eee;
+	background: #fff;
 	border: 1px solid #aaa;
 	-webkit-box-shadow: 2px 2px 3px #ccc;
 	box-shadow: 2px 2px 3px #ccc;
 	left: 0.2em;
 	list-style: none;
 	margin: 0;
-	padding: 0;
+	padding: 4px;
 	position: absolute;
 	top: 1.5em;
 	z-index: 1;
@@ -59,6 +71,7 @@
 	margin: 0;
 	padding: 0.1em 0.4em;
 	width: 100%;
+  text-align: center;
 }
 .jqCron-selector-list li.selected {
 	background: #0088cc;

+ 0 - 19
desktop/core/static/ext/jqCron/LICENSE

@@ -1,19 +0,0 @@
-Copyright (c) 2012-2013 Arnaud Buathier
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is furnished
-to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.

+ 0 - 24
desktop/core/static/ext/jqCron/README.md

@@ -1,24 +0,0 @@
-jqCron
-======
-
-Cron jQuery plugin
-
-Originaly created in july 2012.
-
-Simple documentation available at [this link](http://arnapou.net/2012-07-jquery-cron/)
-
-Features
-========
-* multi select
-* i18n
-* custom binding with dom element (can be two ways sync for instance with input)
-* lots of options (reset button, default value, ...)
-* php class to test matching dates
-
-Examples/screenshots
-====================
-![screen1](/screenshots/example1.png)
-![screen2](/screenshots/example2.png)
-![screen3](/screenshots/example3.png)
-![screen4](/screenshots/example4.png)
-![screen5](/screenshots/cron_php.png)

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 8
desktop/core/static/ext/jqCron/jqCron.min.js


+ 922 - 0
desktop/core/static/js/jqCron.js

@@ -0,0 +1,922 @@
+/*
+ * This file is part of the Arnapou jqCron package.
+ *
+ * (c) Arnaud Buathier <arnaud@arnapou.net>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Default settings
+ */
+var jqCronDefaultSettings = {
+	texts: {
+		fr : {
+			empty: '-tout-',
+			name_minute: 'minute',
+			name_hour: 'heure',
+			name_day: 'jour',
+			name_week: 'semaine',
+			name_month: 'mois',
+			name_year: 'année',
+			text_period: 'Chaque <b />',
+			text_mins: 'à <b /> minutes',
+			text_time: 'à <b />:<b />',
+			text_dow: 'le <b />',
+			text_month: 'de <b />',
+			text_dom: 'le <b />',
+			error1: 'La balise %s n\'est pas supportée !',
+			error2: 'Mauvais nombre d\'éléments',
+			error3: 'La propriété jquery_element doit être définie dans les paramètres jqCron',
+			error4: 'Expression non reconnue',
+			weekdays: ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche'],
+			months: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre']
+		},
+		en : {
+			empty: '-all-',
+			name_minute: 'minute',
+			name_hour: 'hour',
+			name_day: 'day',
+			name_week: 'week',
+			name_month: 'month',
+			name_year: 'year',
+			text_period: 'Every <b />',
+			text_mins: 'at <b /> minutes past the hour',
+			text_time: 'at <b />:<b />',
+			text_dow: 'on <b />',
+			text_month: 'of <b />',
+			text_dom: 'on the <b />',
+			error1: 'The tag %s is not supported !',
+			error2: 'Bad number of elements',
+			error3: 'The jquery_element should be set into jqCron settings',
+			error4: 'Unrecognized expression',
+			weekdays: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
+			months: ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
+		},
+		de : {
+			empty: '-Alle-',
+			name_minute: 'Minute',
+			name_hour: 'Stunde',
+			name_day: 'Tag',
+			name_week: 'Woche',
+			name_month: 'Monat',
+			name_year: 'Jahr',
+			text_period: 'Jede <b />',
+			text_mins: 'um <b /> minuten nach der Stunde',
+			text_time: 'um <b />:<b />',
+			text_dow: 'am <b />',
+			text_month: 'im <b />',
+			text_dom: 'am <b />',
+			error1: 'Der Tag %s wird nicht unterst&uuml;tzt!',
+			error2: 'Ung&uuml;ltige Anzahl an Werten!',
+			error3: 'Das jquery_element sollte in den jqCron-Einstellungen gesetzt werden!',
+			error4: 'Ung&uuml;ltiger Ausdruck',
+			weekdays: ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'],
+			months: ['J&auml;nner', 'Februar', 'M&auml;rz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']
+		}
+	},
+	monthdays: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31],
+	hours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
+	minutes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
+	lang: 'fr',
+  readonly: false,
+	enabled_minute: false,
+	enabled_hour: true,
+	enabled_day: true,
+	enabled_week: true,
+	enabled_month: true,
+	enabled_year: true,
+	multiple_dom: false,
+	multiple_month: false,
+	multiple_mins: false,
+	multiple_dow: false,
+	multiple_time_hours: false,
+	multiple_time_minutes: false,
+	numeric_zero_pad: false,
+	default_period: 'day',
+	default_value: '',
+	no_reset_button: true,
+	disabled: false,
+	bind_to: null,
+	bind_method: {
+		set: function($element, value) {
+			$element.is(':input') ? $element.val(value) : $element.data('jqCronValue', value);
+		},
+		get: function($element) {
+			return $element.is(':input') ? $element.val() : $element.data('jqCronValue');
+		}
+	}
+};
+
+/**
+ * Custom extend of json for jqCron settings.
+ * We don't use jQuery.extend because simple extend does not fit our needs, and deep extend has a bad 
+ * feature for us : it replaces keys of "Arrays" instead of replacing the full array.
+ */
+(function($){
+	var extend = function(dst, src) {
+		for(var i in src) {
+			if($.isPlainObject(src[i])) {
+				dst[i] = extend(dst[i] && $.isPlainObject(dst[i]) ? dst[i] : {}, src[i]);
+			}
+			else if($.isArray(src[i])) {
+				dst[i] = src[i].slice(0);
+			}
+			else if(src[i] !== undefined) {
+				dst[i] = src[i];
+			}
+		}
+		return dst;
+	};
+	this.jqCronMergeSettings = function(obj) {
+		return extend(extend({}, jqCronDefaultSettings), obj || {});
+	};
+}).call(this, jQuery);
+
+/**
+ * Shortcut to get the instance of jqCron instance from one jquery object
+ */
+(function($){
+	$.fn.jqCronGetInstance = function() {
+		return this.data('jqCron');
+	};
+}).call(this, jQuery);
+
+/**
+ * Main plugin
+ */
+(function($){
+	$.fn.jqCron = function(settings) {
+		var saved_settings = settings;
+		return this.each(function() {
+			var cron, saved;
+			var $this = $(this);
+			var settings = jqCronMergeSettings(saved_settings); // clone settings
+			if(!settings.jquery_container) {
+				if($this.is(':container')) {
+					settings.jquery_element = $this.uniqueId('jqCron');
+				}
+				else if($this.is(':autoclose')) {
+					// delete already generated dom if exists
+					if($this.next('.jqCron').length == 1) {
+						$this.next('.jqCron').remove();
+					}
+					// generate new
+					settings.jquery_element = $('<span class="jqCron"></span>').uniqueId('jqCron').insertAfter($this);
+				}
+				else {
+					console && console.error(settings.texts[settings.lang].error1.replace('%s', this.tagName));
+					return;
+				}
+			}
+			
+			// autoset bind_to if it is an input
+			if($this.is(':input')) {
+				settings.bind_to = settings.bind_to || $this;
+			}
+			
+			// init cron object
+			if(settings.bind_to){
+				if(settings.bind_to.is(':input')) {
+					// auto bind from input to object if an input, textarea ...
+					settings.bind_to.blur(function(){
+						var value = settings.bind_method.get(settings.bind_to);
+						$this.jqCronGetInstance().setCron(value);
+					});
+				}
+				saved = settings.bind_method.get(settings.bind_to);
+				cron = new jqCron(settings);
+				cron.setCron(saved);
+			}
+			else {
+				cron = new jqCron(settings);
+			}
+			$(this).data('jqCron', cron);
+		});
+	};
+}).call(this, jQuery);
+
+/**
+ * jqCron class
+ */
+(function($){
+	var jqCronInstances = [];
+	
+	function jqCron(settings) {
+		var _initialized  = false;
+		var _self         = this;
+		var _$elt         = this;
+		var _$obj         = $('<span class="jqCron-container"></span>');
+		var _$blocks      = $('<span class="jqCron-blocks"></span>');
+		var _$blockPERIOD = $('<span class="jqCron-period"></span>');
+		var _$blockDOM    = $('<span class="jqCron-dom"></span>');
+		var _$blockMONTH  = $('<span class="jqCron-month"></span>');
+		var _$blockMINS   = $('<span class="jqCron-mins"></span>');
+		var _$blockDOW    = $('<span class="jqCron-dow"></span>');
+		var _$blockTIME   = $('<span class="jqCron-time"></span>');
+		var _$cross       = $('<span class="jqCron-cross">&#10008;</span>');
+		var _selectors    = [];
+		var _selectorPeriod, _selectorMins, _selectorTimeH, _selectorTimeM, _selectorDow, _selectorDom, _selectorMonth;
+		
+		// instanciate a new selector
+		function newSelector($block, multiple){
+			var selector = new jqCronSelector(_self, $block, multiple);
+			selector.$.bind('selector:open', function(){
+				// we close all opened selectors of all other jqCron
+				for(var n = jqCronInstances.length; n--; ){
+					if(jqCronInstances[n] != _self) {
+						jqCronInstances[n].closeSelectors();
+					}
+					else {
+						// we close all other opened selectors of this jqCron
+						for(var o = _selectors.length; o--; ){
+							if(_selectors[o] != selector) {
+								_selectors[o].close();
+							}
+						}
+					}
+				}
+			});
+			selector.$.bind('selector:change', function(){
+				var boundChanged = false;
+				// don't propagate if not initialized
+				if(!_initialized) return;
+				// bind data between two minute selectors (only if they have the same multiple settings)
+				if(settings.multiple_mins == settings.multiple_time_minutes) {
+					if(selector == _selectorMins) {
+						boundChanged = _selectorTimeM.setValue(_selectorMins.getValue());
+					}
+					else if(selector == _selectorTimeM) {
+						boundChanged = _selectorMins.setValue(_selectorTimeM.getValue());
+					}
+				}
+				// we propagate the change event to the main object
+				boundChanged || _$obj.trigger('cron:change', _self.getCron());
+			});
+			_selectors.push(selector);
+			return selector;
+		}
+		
+		// disable the selector
+		this.disable = function(){
+			_$obj.addClass('disable');
+			settings.disable = true;
+			_self.closeSelectors();
+		};
+
+    // make the selector readonly
+		this.enableReadonly = function(){
+			_$obj.addClass('readonly');
+			settings.readonly = true;
+			_self.closeSelectors();
+		};
+		
+		// return if the selector is disabled
+		this.isDisabled = function() {
+			return settings.disable == true;
+		};
+
+    this.isReadonly = function() {
+			return settings.readonly == true;
+		};
+		
+		// enable the selector
+		this.enable = function(){
+			_$obj.removeClass('disable');
+			settings.disable = false;
+		};
+
+		this.disableReadonly = function(){
+			_$obj.removeClass('readonly');
+			settings.readonly = false;
+		};
+		
+		// get cron value
+		this.getCron = function(){
+			var period = _selectorPeriod.getValue();
+			var items = ['*', '*', '*', '*', '*'];
+			if(period == 'hour') {
+				items[0] = _selectorMins.getCronValue();
+			}
+			if(period == 'day' || period == 'week' || period == 'month' || period == 'year') {
+				items[0] = _selectorTimeM.getCronValue();
+				items[1] = _selectorTimeH.getCronValue();
+			}
+			if(period == 'month' || period == 'year') {
+				items[2] = _selectorDom.getCronValue();
+			}
+			if(period == 'year') {
+				items[3] = _selectorMonth.getCronValue();
+			}
+			if(period == 'week') {
+				items[4] = _selectorDow.getCronValue();
+			}
+			return items.join(' ');
+		};
+		
+		// set cron (string like * * * * *)
+		this.setCron = function(str) {
+			if(!str) return;
+			try {
+				str = str.replace(/\s+/g, ' ').replace(/^ +/, '').replace(/ +$/, ''); // sanitize
+				var mask = str.replace(/[^\* ]/g, '-').replace(/-+/g, '-').replace(/ +/g, '');
+				var items = str.split(' ');
+				if (items.length != 5) _self.error(_self.getText('error2'));
+				if(mask == '*****') {						// 1 possibility
+					_selectorPeriod.setValue('minute');
+				}
+				else if(mask == '-****') {					// 1 possibility
+					_selectorPeriod.setValue('hour');
+					_selectorMins.setCronValue(items[0]);
+					_selectorTimeM.setCronValue(items[0]);
+				}
+				else if(mask.substring(2, mask.length) == '***') {			// 4 possibilities
+					_selectorPeriod.setValue('day');
+					_selectorMins.setCronValue(items[0]);
+					_selectorTimeM.setCronValue(items[0]);
+					_selectorTimeH.setCronValue(items[1]);
+				}
+				else if(mask.substring(2, mask.length) == '-**') {			// 4 possibilities
+					_selectorPeriod.setValue('month');
+					_selectorMins.setCronValue(items[0]);
+					_selectorTimeM.setCronValue(items[0]);
+					_selectorTimeH.setCronValue(items[1]);
+					_selectorDom.setCronValue(items[2]);
+				}
+				else if(mask.substring(2, mask.length) == '**-') {			// 4 possibilities
+					_selectorPeriod.setValue('week');
+					_selectorMins.setCronValue(items[0]);
+					_selectorTimeM.setCronValue(items[0]);
+					_selectorTimeH.setCronValue(items[1]);
+					_selectorDow.setCronValue(items[4]);
+				}
+				else if (mask.substring(3, mask.length) == '-*') {			// 8 possibilities
+					_selectorPeriod.setValue('year');
+					_selectorMins.setCronValue(items[0]);
+					_selectorTimeM.setCronValue(items[0]);
+					_selectorTimeH.setCronValue(items[1]);
+					_selectorDom.setCronValue(items[2]);
+					_selectorMonth.setCronValue(items[3]);
+				}
+				else {
+					_self.error(_self.getText('error4'));
+				}
+				_self.clearError();
+			} catch(e) {}
+		};
+		
+		// close all child selectors
+		this.closeSelectors = function(){
+			for(var n = _selectors.length; n--; ){
+				_selectors[n].close();
+			}
+		};
+	
+		// get the main element id
+		this.getId = function(){
+			return _$elt.attr('id');
+		}
+	
+		// get the translated text
+		this.getText = function(key) {
+			var text = settings.texts[settings.lang][key] || null;
+			if(typeof(text) == "string" && text.match('<b')){
+				text = text.replace(/(<b *\/>)/gi, '</span><b /><span class="jqCron-text">');
+				text = '<span class="jqCron-text">' + text + '</span>';
+			}
+			return text;
+		};
+		
+		// get the human readable text
+		this.getHumanText = function() {
+		    var texts=[];
+		    _$obj
+		    .find('> span > span:visible')
+		    .find('.jqCron-text, .jqCron-selector > span')
+		    .each(function() {
+		        var text = $(this).text().replace(/\s+$/g, '').replace(/^\s+/g, '');
+		        text && texts.push(text);
+		    });
+		    return texts.join(' ').replace(/\s:\s/g, ':');
+		}
+	
+		// get settings
+		this.getSettings = function(){
+			return settings;
+		};
+	
+		// display an error
+		this.error = function(msg) {
+			console && console.error('[jqCron Error] ' + msg);
+			_$obj.addClass('jqCron-error').attr('title', msg);
+			throw msg;
+		};
+		
+		// clear error
+		this.clearError = function(){
+			_$obj.attr('title', '').removeClass('jqCron-error');
+		};
+	
+		// clear
+		this.clear = function() {
+			_selectorDom.setValue([]);
+			_selectorDow.setValue([]);
+			_selectorMins.setValue([]);
+			_selectorMonth.setValue([]);
+			_selectorTimeH.setValue([]);
+			_selectorTimeM.setValue([]);
+			_self.triggerChange();
+		};
+		
+		// init (called in constructor)
+		this.init = function(){
+			var n,i,list;
+			if(_initialized) return;
+			
+			settings = jqCronMergeSettings(settings);
+			settings.jquery_element || _self.error(_self.getText('error3'));
+			_$elt = settings.jquery_element;
+			_$elt.append(_$obj);
+			_$obj.data('id', settings.id);
+			_$obj.data('jqCron', _self);
+			_$obj.append(_$blocks);
+			settings.no_reset_button || _$obj.append(_$cross);
+			(!settings.disable) || _$obj.addClass('disable');
+			(!settings.readonly) || _$obj.addClass('readonly');
+			_$blocks.append(_$blockPERIOD);
+			_$blocks.append(_$blockDOM);
+			_$blocks.append(_$blockMONTH);
+			_$blocks.append(_$blockMINS);
+			_$blocks.append(_$blockDOW);
+			_$blocks.append(_$blockTIME);
+			
+			// various binding
+			_$cross.click(function(){
+				_self.isDisabled() || _self.clear();
+			});
+			
+			// binding from cron to target
+			_$obj.bind('cron:change', function(evt, value){
+				if(!settings.bind_to) return;
+				settings.bind_method.set && settings.bind_method.set(settings.bind_to, value);
+				_self.clearError();
+			});
+		
+			// PERIOD
+			_$blockPERIOD.append(_self.getText('text_period'));
+			_selectorPeriod = newSelector(_$blockPERIOD, false);
+			settings.enabled_minute && _selectorPeriod.add('minute', _self.getText('name_minute'));
+			settings.enabled_hour   && _selectorPeriod.add('hour',   _self.getText('name_hour'));
+			settings.enabled_day    && _selectorPeriod.add('day',    _self.getText('name_day'));
+			settings.enabled_week   && _selectorPeriod.add('week',   _self.getText('name_week'));
+			settings.enabled_month  && _selectorPeriod.add('month',  _self.getText('name_month'));
+			settings.enabled_year   && _selectorPeriod.add('year',   _self.getText('name_year'));
+			_selectorPeriod.$.bind('selector:change', function(e, value){
+				_$blockDOM.hide();
+				_$blockMONTH.hide();
+				_$blockMINS.hide();
+				_$blockDOW.hide();
+				_$blockTIME.hide();
+				if(value == 'hour') {
+					_$blockMINS.show();
+				}
+				else if(value == 'day') {
+					_$blockTIME.show();
+				}
+				else if(value == 'week') {
+					_$blockDOW.show();
+					_$blockTIME.show();
+				}
+				else if(value == 'month') {
+					_$blockDOM.show();
+					_$blockTIME.show();
+				}
+				else if(value == 'year') {
+					_$blockDOM.show();
+					_$blockMONTH.show();
+					_$blockTIME.show();
+				}
+			});
+			_selectorPeriod.setValue(settings.default_period);
+		
+			// MINS  (minutes)
+			_$blockMINS.append(_self.getText('text_mins'));
+			_selectorMins = newSelector(_$blockMINS, settings.multiple_mins);
+			for(i=0, list=settings.minutes; i<list.length; i++){
+				_selectorMins.add(list[i], list[i]);
+			}
+		
+			// TIME  (hour:min)
+			_$blockTIME.append(_self.getText('text_time'));
+			_selectorTimeH = newSelector(_$blockTIME, settings.multiple_time_hours);
+			for(i=0, list=settings.hours; i<list.length; i++){
+				_selectorTimeH.add(list[i], list[i]);
+			}
+			_selectorTimeM = newSelector(_$blockTIME, settings.multiple_time_minutes);
+			for(i=0, list=settings.minutes; i<list.length; i++){
+				_selectorTimeM.add(list[i], list[i]);
+			}
+		
+			// DOW  (day of week)
+			_$blockDOW.append(_self.getText('text_dow'));
+			_selectorDow = newSelector(_$blockDOW, settings.multiple_dow);
+			for(i=0, list=_self.getText('weekdays'); i<list.length; i++){
+				_selectorDow.add(i+1, list[i]);
+			}
+		
+			// DOM  (day of month)
+			_$blockDOM.append(_self.getText('text_dom'));
+			_selectorDom = newSelector(_$blockDOM, settings.multiple_dom);
+			for(i=0, list=settings.monthdays; i<list.length; i++){
+				_selectorDom.add(list[i], list[i]);
+			}
+		
+			// MONTH  (day of week)
+			_$blockMONTH.append(_self.getText('text_month'));
+			_selectorMonth = newSelector(_$blockMONTH, settings.multiple_month);
+			for(i=0, list=_self.getText('months'); i<list.length; i++){
+				_selectorMonth.add(i+1, list[i]);
+			}
+		
+			// close all selectors when we click in body
+			$('body').click(function(){
+				var i, n = _selectors.length;
+				for(i = 0; i < n; i++){
+					_selectors[i].close();
+				}
+			});
+			_initialized = true;
+			
+			// default value
+			if(settings.default_value) {
+				_self.setCron(settings.default_value);
+			}
+		};
+
+		// trigger a change event
+		this.triggerChange = function(){
+			_$obj.trigger('cron:change', _self.getCron());
+		};
+		
+		// store instance in array
+		jqCronInstances.push(this);
+		
+		// expose main jquery object
+		this.$ = _$obj;
+		
+		// init
+		try {
+			this.init();
+			_self.triggerChange();
+		} catch(e){}
+	}
+	this.jqCron = jqCron;
+}).call(this, jQuery);
+
+
+/**
+ * jqCronSelector class
+ */
+(function($){
+	function jqCronSelector(_cron, _$block, _multiple){
+		var _self      = this;
+		var _$list     = $('<ul class="jqCron-selector-list"></ul>');
+		var _$title    = $('<span class="jqCron-selector-title"></span>');
+		var _$selector = $('<span class="jqCron-selector"></span>');
+		var _values    = {};
+		var _value     = [];
+		var _hasNumericTexts = true;
+		var _numeric_zero_pad = _cron.getSettings().numeric_zero_pad;
+		
+		// return an array without doublon
+		function array_unique(l){ 
+			var i=0,n=l.length,k={},a=[];
+			while(i<n) {
+				k[l[i]] || (k[l[i]] = 1 && a.push(l[i]));
+				i++;
+			}
+			return a;
+		}
+	
+		// get the value (an array if multiple, else a single value)
+		this.getValue = function(){
+			return _multiple ? _value : _value[0];
+		};
+		
+		// get a correct string for cron
+		this.getCronValue = function(){
+			if(_value.length == 0) return '*';
+			var cron = [_value[0]], i, s = _value[0], c = _value[0], n = _value.length;
+			for(i=1; i<n; i++) {
+				if(_value[i] == c+1) {
+					c = _value[i];
+					cron[cron.length-1] = s+'-'+c;
+				}
+				else {
+					s = c = _value[i];
+					cron.push(c);
+				}
+			}
+			return cron.join(',');
+		};
+		
+		// set the cron value 
+		this.setCronValue = function(str) {
+			var values = [], m ,i, n;
+			if(str !== '*') {
+				while(str != '') {
+					// test "*/n" expression
+					m = str.match(/^\*\/([0-9]+),?/);
+					if(m && m.length == 2) {
+						for(i=0; i<=59; i+=(m[1]|0)) {
+							values.push(i);
+						}
+						str = str.replace(m[0], '');
+						continue;
+					}
+					// test "a-b/n" expression
+					m = str.match(/^([0-9]+)-([0-9]+)\/([0-9]+),?/);
+					if(m && m.length == 4) {
+						for(i=(m[1]|0); i<=(m[2]|0); i+=(m[3]|0)) {
+							values.push(i);
+						}
+						str = str.replace(m[0], '');
+						continue;
+					}
+					// test "a-b" expression
+					m = str.match(/^([0-9]+)-([0-9]+),?/);
+					if(m && m.length == 3) {
+						for(i=(m[1]|0); i<=(m[2]|0); i++) {
+							values.push(i);
+						}
+						str = str.replace(m[0], '');
+						continue;
+					}
+					// test "c" expression
+					m = str.match(/^([0-9]+),?/);
+					if(m && m.length == 2) {
+						values.push(m[1]|0);
+						str = str.replace(m[0], '');
+						continue;
+					}
+					// something goes wrong in the expression
+					return ;
+				}
+			}
+			_self.setValue(values);
+		};
+	
+		// close the selector
+		this.close = function(){
+			_$selector.trigger('selector:close');
+		};
+		
+		// open the selector
+		this.open = function(){
+			_$selector.trigger('selector:open');
+		};
+	
+		// whether the selector is open
+		this.isOpened = function() {
+			return _$list.is(':visible');
+		};
+	
+		// add a selected value to the list
+		this.addValue = function(key) {
+			var values = _multiple ? _value.slice(0) : []; // clone array
+			values.push(key);
+			_self.setValue(values);
+		};
+	
+		// remove a selected value from the list
+		this.removeValue = function(key) {
+			if(_multiple) {
+				var i, newValue = [];
+				for(i=0; i<_value.length; i++){
+					if(key != [_value[i]]) {
+						newValue.push(_value[i]);
+					}
+				}
+				_self.setValue(newValue);
+			}
+			else {
+				_self.clear();
+			}
+		};
+	
+		// set the selected value(s) of the list
+		this.setValue = function(keys){
+			var i, newKeys = [], saved = _value.join(' ');
+			if(!$.isArray(keys)) keys = [keys];
+			_$list.find('li').removeClass('selected');
+			keys = array_unique(keys);
+			keys.sort(function(a, b){ 
+				var ta = typeof(a);
+				var tb = typeof(b);
+				if(ta==tb && ta=="number") return a-b;
+				else return String(a) == String(b) ? 0 : (String(a) < String(b) ? -1 : 1);
+			});
+			if(_multiple) {
+				for(i=0; i<keys.length; i++){
+					if(keys[i] in _values) {
+						_values[keys[i]].addClass('selected');
+						newKeys.push(keys[i]);
+					}
+				}
+			}
+			else {
+				if(keys[0] in _values) {
+					_values[keys[0]].addClass('selected');
+					newKeys.push(keys[0]);
+				}
+			}
+			// remove unallowed values
+			_value = newKeys;
+			if(saved != _value.join(' ')) {
+				_$selector.trigger('selector:change', _multiple ? keys : keys[0]);
+				return true;
+			}
+			return false;
+		};
+		
+		// get the title text
+		this.getTitleText = function(){
+			var getValueText = function(key) {
+				return (key in _values) ? _values[key].text() : key;
+			};
+			
+			if(_value.length == 0) return _cron.getText('empty');
+			var cron = [getValueText(_value[0])], i, s = _value[0], c = _value[0], n = _value.length;
+			for(i=1; i<n; i++) {
+				if(_value[i] == c+1) {
+					c = _value[i];
+					cron[cron.length-1] = getValueText(s)+'-'+getValueText(c);
+				}
+				else {
+					s = c = _value[i];
+					cron.push(getValueText(c));
+				}
+			}
+			return cron.join(',');
+		};
+	
+		// clear list
+		this.clear = function() {
+			_values = {};
+			_self.setValue([]);
+			_$list.empty();
+		};
+	
+		// add a (key, value) pair
+		this.add = function(key, value) {
+			if(!(value+'').match(/^[0-9]+$/)) _hasNumericTexts = false;
+			if(_numeric_zero_pad && _hasNumericTexts && value < 10) {
+				value = '0'+value;
+			}
+			var $item = $('<li>' + value + '</li>');
+			_$list.append($item);
+			_values[key] = $item;
+			$item.click(function(){
+				if(_multiple && $(this).hasClass('selected')) {
+					_self.removeValue(key);
+				}
+				else {
+					_self.addValue(key);
+					if(!_multiple) _self.close();
+				}
+			});
+		};
+		
+		// expose main jquery object
+		this.$ = _$selector;
+	
+		// constructor
+		_$block.find('b:eq(0)').after(_$selector).remove();
+		_$selector
+		.addClass('jqCron-selector-' + _$block.find('.jqCron-selector').length)
+		.append(_$title)
+		.append(_$list)
+		.bind('selector:open', function(){
+			if(_hasNumericTexts) {
+				var nbcols = 1, n = _$list.find('li').length;
+				if(n > 5 && n <= 16) nbcols = 2;
+				else if(n > 16 && n <= 23) nbcols = 3;
+				else if(n > 23 && n <= 40) nbcols = 4;
+				else if(n > 40) nbcols = 5;
+				_$list.addClass('cols'+nbcols);
+			}
+			_$list.show();
+		})
+		.bind('selector:close', function(){
+			_$list.hide();
+		})
+		.bind('selector:change', function(){
+			_$title.html(_self.getTitleText());
+		})
+		.click(function(e){
+			e.stopPropagation();
+		})
+		.trigger('selector:change')
+		;
+		$.fn.disableSelection && _$selector.disableSelection(); // only work with jQuery UI
+		_$title.click(function(e){
+			(_self.isOpened() || _cron.isDisabled() || _cron.isReadonly()) ? _self.close() : _self.open();
+		});
+		_self.close();
+		_self.clear();
+	}
+	this.jqCronSelector = jqCronSelector;
+}).call(this, jQuery);
+
+/**
+ * Generate unique id for each element.
+ * Skip elements which have already an id.
+ */
+(function($){
+	var jqUID = 0;
+	var jqGetUID = function(prefix){
+		var id;
+		while(1) {
+			jqUID++;
+			id = ((prefix || 'JQUID')+'') + jqUID;
+			if(!document.getElementById(id)) return id;
+		}
+	};
+	$.fn.uniqueId =  function(prefix) {
+		return this.each(function(){
+			if($(this).attr('id')) return;
+			var id = jqGetUID(prefix);
+			$(this).attr('id', id);
+		});
+	};
+}).call(this, jQuery);
+
+
+/**
+ * Extends jQuery selectors with new block selector
+ */
+(function($){
+	$.extend($.expr[':'], {
+		container: function(a) {
+			return (a.tagName+'').toLowerCase() in {
+				a:1,
+				abbr:1,
+				acronym:1,
+				address:1,
+				b:1,
+				big:1,
+				blockquote:1,
+				button:1,
+				cite:1,
+				code:1,
+				dd: 1,
+				del:1,
+				dfn:1,
+				div:1,
+				dt:1,
+				em:1,
+				fieldset:1,
+				form:1,
+				h1:1,
+				h2:1,
+				h3:1,
+				h4:1,
+				h5:1,
+				h6: 1,
+				i:1,
+				ins:1,
+				kbd:1,
+				label:1,
+				li:1,
+				p:1,
+				pre:1,
+				q:1,
+				samp:1,
+				small:1,
+				span:1,
+				strong:1,
+				sub: 1,
+				sup:1,
+				td:1,
+				tt:1
+			};
+		},
+		autoclose: function(a) {
+			return (a.tagName+'').toLowerCase() in {
+				area:1,
+				base:1,
+				basefont:1,
+				br:1,
+				col:1,
+				frame:1,
+				hr:1,
+				img:1,
+				input:1,
+				link:1,
+				meta:1,
+				param:1
+			};
+		}
+	});
+}).call(this, jQuery);

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.