├── FS-rpc.py
├── README
├── __init__.py
├── bin
├── jquery-1.3.1.min.js
├── json2.js
├── trimpath-template-1.0.38.js
├── ui.core.js
└── ui.sortable.js
├── conference
├── __init__.py
├── admin.py
├── models.py
├── urls.py
└── views.py
├── debug.py
├── dialplan
├── __init__.py
├── admin.py
├── models.py
├── templates
│ ├── condition_inline.html
│ ├── dialplan.html
│ └── dialplan.xml
├── urls.py
└── views.py
├── eventsocket
├── InboundOutboundExample.py
├── SimpleOriginateExample.py
├── __init__.py
├── eventsocket.py
└── kuku.py
├── fs2web.wsgi
├── fsapi.py
├── local_settings.py.template
├── locale
└── ru
│ └── LC_MESSAGES
│ ├── django.mo
│ └── django.po
├── manage.py
├── settings.py
├── superdict.py
├── templates
├── base.html
├── confrences.html
├── group.xml
├── index.html
└── user.xml
├── urls.py
└── users
├── __init__.py
├── admin.py
├── models.py
├── templates
└── users
│ ├── fsuser_detail.html
│ └── fsuser_edit.html
├── urls.py
└── views.py
/FS-rpc.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | from xmlrpclib import ServerProxy
4 |
5 | host = 'localhost'
6 | username = 'freeswitch'
7 | password = 'works'
8 | port = '8080'
9 |
10 | server = ServerProxy("http://%s:%s@%s:%s" % (username, password, host, port))
11 | print server.freeswitch.api("show","channels")
12 |
13 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | ==============
2 | Fs2web
3 | ==============
4 |
5 | 1. Introduction_
6 | 2. Requirements_
7 | 3. Installation_
8 |
9 | Introduction
10 | ============
11 |
12 | Fs2web is the Django based Web UI for FreeSWITCH.
13 |
14 | Django: http://www.djangoproject.com/
15 | FreeSWITCH: http://freeswitch.org
16 |
17 | Requirements
18 | ============
19 |
20 | - Django >= 1.0
21 | - FreeSWITCH - svn trunk
22 |
23 | Installation
24 | ============
25 |
26 | Запуск - cd fs2web; ./manage.py runserver
27 |
28 | Для редактирования настроек надо зайти в административный интерфейс: http://127.0.0.1:8000/admin/
29 | Логин admin, пароль kuku.
30 |
31 | В conf/autoload_configs/xml_curl.conf.xml:
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | И включить загрузку модуля xml_curl в conf/autoload_configs/modules.conf.xml
46 |
47 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Deepwalker/fs2web/a8807ae30d02d4a11e47fdbf9a9948a1bea8c1d4/__init__.py
--------------------------------------------------------------------------------
/bin/json2.js:
--------------------------------------------------------------------------------
1 | /*
2 | http://www.JSON.org/json2.js
3 | 2008-11-19
4 |
5 | Public Domain.
6 |
7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
8 |
9 | See http://www.JSON.org/js.html
10 |
11 | This file creates a global JSON object containing two methods: stringify
12 | and parse.
13 |
14 | JSON.stringify(value, replacer, space)
15 | value any JavaScript value, usually an object or array.
16 |
17 | replacer an optional parameter that determines how object
18 | values are stringified for objects. It can be a
19 | function or an array of strings.
20 |
21 | space an optional parameter that specifies the indentation
22 | of nested structures. If it is omitted, the text will
23 | be packed without extra whitespace. If it is a number,
24 | it will specify the number of spaces to indent at each
25 | level. If it is a string (such as '\t' or ' '),
26 | it contains the characters used to indent at each level.
27 |
28 | This method produces a JSON text from a JavaScript value.
29 |
30 | When an object value is found, if the object contains a toJSON
31 | method, its toJSON method will be called and the result will be
32 | stringified. A toJSON method does not serialize: it returns the
33 | value represented by the name/value pair that should be serialized,
34 | or undefined if nothing should be serialized. The toJSON method
35 | will be passed the key associated with the value, and this will be
36 | bound to the object holding the key.
37 |
38 | For example, this would serialize Dates as ISO strings.
39 |
40 | Date.prototype.toJSON = function (key) {
41 | function f(n) {
42 | // Format integers to have at least two digits.
43 | return n < 10 ? '0' + n : n;
44 | }
45 |
46 | return this.getUTCFullYear() + '-' +
47 | f(this.getUTCMonth() + 1) + '-' +
48 | f(this.getUTCDate()) + 'T' +
49 | f(this.getUTCHours()) + ':' +
50 | f(this.getUTCMinutes()) + ':' +
51 | f(this.getUTCSeconds()) + 'Z';
52 | };
53 |
54 | You can provide an optional replacer method. It will be passed the
55 | key and value of each member, with this bound to the containing
56 | object. The value that is returned from your method will be
57 | serialized. If your method returns undefined, then the member will
58 | be excluded from the serialization.
59 |
60 | If the replacer parameter is an array of strings, then it will be
61 | used to select the members to be serialized. It filters the results
62 | such that only members with keys listed in the replacer array are
63 | stringified.
64 |
65 | Values that do not have JSON representations, such as undefined or
66 | functions, will not be serialized. Such values in objects will be
67 | dropped; in arrays they will be replaced with null. You can use
68 | a replacer function to replace those with JSON values.
69 | JSON.stringify(undefined) returns undefined.
70 |
71 | The optional space parameter produces a stringification of the
72 | value that is filled with line breaks and indentation to make it
73 | easier to read.
74 |
75 | If the space parameter is a non-empty string, then that string will
76 | be used for indentation. If the space parameter is a number, then
77 | the indentation will be that many spaces.
78 |
79 | Example:
80 |
81 | text = JSON.stringify(['e', {pluribus: 'unum'}]);
82 | // text is '["e",{"pluribus":"unum"}]'
83 |
84 |
85 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
86 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
87 |
88 | text = JSON.stringify([new Date()], function (key, value) {
89 | return this[key] instanceof Date ?
90 | 'Date(' + this[key] + ')' : value;
91 | });
92 | // text is '["Date(---current time---)"]'
93 |
94 |
95 | JSON.parse(text, reviver)
96 | This method parses a JSON text to produce an object or array.
97 | It can throw a SyntaxError exception.
98 |
99 | The optional reviver parameter is a function that can filter and
100 | transform the results. It receives each of the keys and values,
101 | and its return value is used instead of the original value.
102 | If it returns what it received, then the structure is not modified.
103 | If it returns undefined then the member is deleted.
104 |
105 | Example:
106 |
107 | // Parse the text. Values that look like ISO date strings will
108 | // be converted to Date objects.
109 |
110 | myData = JSON.parse(text, function (key, value) {
111 | var a;
112 | if (typeof value === 'string') {
113 | a =
114 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
115 | if (a) {
116 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
117 | +a[5], +a[6]));
118 | }
119 | }
120 | return value;
121 | });
122 |
123 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
124 | var d;
125 | if (typeof value === 'string' &&
126 | value.slice(0, 5) === 'Date(' &&
127 | value.slice(-1) === ')') {
128 | d = new Date(value.slice(5, -1));
129 | if (d) {
130 | return d;
131 | }
132 | }
133 | return value;
134 | });
135 |
136 |
137 | This is a reference implementation. You are free to copy, modify, or
138 | redistribute.
139 |
140 | This code should be minified before deployment.
141 | See http://javascript.crockford.com/jsmin.html
142 |
143 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
144 | NOT CONTROL.
145 | */
146 |
147 | /*jslint evil: true */
148 |
149 | /*global JSON */
150 |
151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
154 | lastIndex, length, parse, prototype, push, replace, slice, stringify,
155 | test, toJSON, toString, valueOf
156 | */
157 |
158 | // Create a JSON object only if one does not already exist. We create the
159 | // methods in a closure to avoid creating global variables.
160 |
161 | if (!this.JSON) {
162 | JSON = {};
163 | }
164 | (function () {
165 |
166 | function f(n) {
167 | // Format integers to have at least two digits.
168 | return n < 10 ? '0' + n : n;
169 | }
170 |
171 | if (typeof Date.prototype.toJSON !== 'function') {
172 |
173 | Date.prototype.toJSON = function (key) {
174 |
175 | return this.getUTCFullYear() + '-' +
176 | f(this.getUTCMonth() + 1) + '-' +
177 | f(this.getUTCDate()) + 'T' +
178 | f(this.getUTCHours()) + ':' +
179 | f(this.getUTCMinutes()) + ':' +
180 | f(this.getUTCSeconds()) + 'Z';
181 | };
182 |
183 | String.prototype.toJSON =
184 | Number.prototype.toJSON =
185 | Boolean.prototype.toJSON = function (key) {
186 | return this.valueOf();
187 | };
188 | }
189 |
190 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
191 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
192 | gap,
193 | indent,
194 | meta = { // table of character substitutions
195 | '\b': '\\b',
196 | '\t': '\\t',
197 | '\n': '\\n',
198 | '\f': '\\f',
199 | '\r': '\\r',
200 | '"' : '\\"',
201 | '\\': '\\\\'
202 | },
203 | rep;
204 |
205 |
206 | function quote(string) {
207 |
208 | // If the string contains no control characters, no quote characters, and no
209 | // backslash characters, then we can safely slap some quotes around it.
210 | // Otherwise we must also replace the offending characters with safe escape
211 | // sequences.
212 |
213 | escapable.lastIndex = 0;
214 | return escapable.test(string) ?
215 | '"' + string.replace(escapable, function (a) {
216 | var c = meta[a];
217 | return typeof c === 'string' ? c :
218 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
219 | }) + '"' :
220 | '"' + string + '"';
221 | }
222 |
223 |
224 | function str(key, holder) {
225 |
226 | // Produce a string from holder[key].
227 |
228 | var i, // The loop counter.
229 | k, // The member key.
230 | v, // The member value.
231 | length,
232 | mind = gap,
233 | partial,
234 | value = holder[key];
235 |
236 | // If the value has a toJSON method, call it to obtain a replacement value.
237 |
238 | if (value && typeof value === 'object' &&
239 | typeof value.toJSON === 'function') {
240 | value = value.toJSON(key);
241 | }
242 |
243 | // If we were called with a replacer function, then call the replacer to
244 | // obtain a replacement value.
245 |
246 | if (typeof rep === 'function') {
247 | value = rep.call(holder, key, value);
248 | }
249 |
250 | // What happens next depends on the value's type.
251 |
252 | switch (typeof value) {
253 | case 'string':
254 | return quote(value);
255 |
256 | case 'number':
257 |
258 | // JSON numbers must be finite. Encode non-finite numbers as null.
259 |
260 | return isFinite(value) ? String(value) : 'null';
261 |
262 | case 'boolean':
263 | case 'null':
264 |
265 | // If the value is a boolean or null, convert it to a string. Note:
266 | // typeof null does not produce 'null'. The case is included here in
267 | // the remote chance that this gets fixed someday.
268 |
269 | return String(value);
270 |
271 | // If the type is 'object', we might be dealing with an object or an array or
272 | // null.
273 |
274 | case 'object':
275 |
276 | // Due to a specification blunder in ECMAScript, typeof null is 'object',
277 | // so watch out for that case.
278 |
279 | if (!value) {
280 | return 'null';
281 | }
282 |
283 | // Make an array to hold the partial results of stringifying this object value.
284 |
285 | gap += indent;
286 | partial = [];
287 |
288 | // Is the value an array?
289 |
290 | if (Object.prototype.toString.apply(value) === '[object Array]') {
291 |
292 | // The value is an array. Stringify every element. Use null as a placeholder
293 | // for non-JSON values.
294 |
295 | length = value.length;
296 | for (i = 0; i < length; i += 1) {
297 | partial[i] = str(i, value) || 'null';
298 | }
299 |
300 | // Join all of the elements together, separated with commas, and wrap them in
301 | // brackets.
302 |
303 | v = partial.length === 0 ? '[]' :
304 | gap ? '[\n' + gap +
305 | partial.join(',\n' + gap) + '\n' +
306 | mind + ']' :
307 | '[' + partial.join(',') + ']';
308 | gap = mind;
309 | return v;
310 | }
311 |
312 | // If the replacer is an array, use it to select the members to be stringified.
313 |
314 | if (rep && typeof rep === 'object') {
315 | length = rep.length;
316 | for (i = 0; i < length; i += 1) {
317 | k = rep[i];
318 | if (typeof k === 'string') {
319 | v = str(k, value);
320 | if (v) {
321 | partial.push(quote(k) + (gap ? ': ' : ':') + v);
322 | }
323 | }
324 | }
325 | } else {
326 |
327 | // Otherwise, iterate through all of the keys in the object.
328 |
329 | for (k in value) {
330 | if (Object.hasOwnProperty.call(value, k)) {
331 | v = str(k, value);
332 | if (v) {
333 | partial.push(quote(k) + (gap ? ': ' : ':') + v);
334 | }
335 | }
336 | }
337 | }
338 |
339 | // Join all of the member texts together, separated with commas,
340 | // and wrap them in braces.
341 |
342 | v = partial.length === 0 ? '{}' :
343 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
344 | mind + '}' : '{' + partial.join(',') + '}';
345 | gap = mind;
346 | return v;
347 | }
348 | }
349 |
350 | // If the JSON object does not yet have a stringify method, give it one.
351 |
352 | if (typeof JSON.stringify !== 'function') {
353 | JSON.stringify = function (value, replacer, space) {
354 |
355 | // The stringify method takes a value and an optional replacer, and an optional
356 | // space parameter, and returns a JSON text. The replacer can be a function
357 | // that can replace values, or an array of strings that will select the keys.
358 | // A default replacer method can be provided. Use of the space parameter can
359 | // produce text that is more easily readable.
360 |
361 | var i;
362 | gap = '';
363 | indent = '';
364 |
365 | // If the space parameter is a number, make an indent string containing that
366 | // many spaces.
367 |
368 | if (typeof space === 'number') {
369 | for (i = 0; i < space; i += 1) {
370 | indent += ' ';
371 | }
372 |
373 | // If the space parameter is a string, it will be used as the indent string.
374 |
375 | } else if (typeof space === 'string') {
376 | indent = space;
377 | }
378 |
379 | // If there is a replacer, it must be a function or an array.
380 | // Otherwise, throw an error.
381 |
382 | rep = replacer;
383 | if (replacer && typeof replacer !== 'function' &&
384 | (typeof replacer !== 'object' ||
385 | typeof replacer.length !== 'number')) {
386 | throw new Error('JSON.stringify');
387 | }
388 |
389 | // Make a fake root object containing our value under the key of ''.
390 | // Return the result of stringifying the value.
391 |
392 | return str('', {'': value});
393 | };
394 | }
395 |
396 |
397 | // If the JSON object does not yet have a parse method, give it one.
398 |
399 | if (typeof JSON.parse !== 'function') {
400 | JSON.parse = function (text, reviver) {
401 |
402 | // The parse method takes a text and an optional reviver function, and returns
403 | // a JavaScript value if the text is a valid JSON text.
404 |
405 | var j;
406 |
407 | function walk(holder, key) {
408 |
409 | // The walk method is used to recursively walk the resulting structure so
410 | // that modifications can be made.
411 |
412 | var k, v, value = holder[key];
413 | if (value && typeof value === 'object') {
414 | for (k in value) {
415 | if (Object.hasOwnProperty.call(value, k)) {
416 | v = walk(value, k);
417 | if (v !== undefined) {
418 | value[k] = v;
419 | } else {
420 | delete value[k];
421 | }
422 | }
423 | }
424 | }
425 | return reviver.call(holder, key, value);
426 | }
427 |
428 |
429 | // Parsing happens in four stages. In the first stage, we replace certain
430 | // Unicode characters with escape sequences. JavaScript handles many characters
431 | // incorrectly, either silently deleting them, or treating them as line endings.
432 |
433 | cx.lastIndex = 0;
434 | if (cx.test(text)) {
435 | text = text.replace(cx, function (a) {
436 | return '\\u' +
437 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
438 | });
439 | }
440 |
441 | // In the second stage, we run the text against regular expressions that look
442 | // for non-JSON patterns. We are especially concerned with '()' and 'new'
443 | // because they can cause invocation, and '=' because it can cause mutation.
444 | // But just to be safe, we want to reject all unexpected forms.
445 |
446 | // We split the second stage into 4 regexp operations in order to work around
447 | // crippling inefficiencies in IE's and Safari's regexp engines. First we
448 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
449 | // replace all simple value tokens with ']' characters. Third, we delete all
450 | // open brackets that follow a colon or comma or that begin the text. Finally,
451 | // we look to see that the remaining characters are only whitespace or ']' or
452 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
453 |
454 | if (/^[\],:{}\s]*$/.
455 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
456 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
457 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
458 |
459 | // In the third stage we use the eval function to compile the text into a
460 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
461 | // in JavaScript: it can begin a block or an object literal. We wrap the text
462 | // in parens to eliminate the ambiguity.
463 |
464 | j = eval('(' + text + ')');
465 |
466 | // In the optional fourth stage, we recursively walk the new structure, passing
467 | // each name/value pair to a reviver function for possible transformation.
468 |
469 | return typeof reviver === 'function' ?
470 | walk({'': j}, '') : j;
471 | }
472 |
473 | // If the text is not JSON parseable, then a SyntaxError is thrown.
474 |
475 | throw new SyntaxError('JSON.parse');
476 | };
477 | }
478 | })();
479 |
--------------------------------------------------------------------------------
/bin/trimpath-template-1.0.38.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TrimPath Template. Release 1.0.38.
3 | * Copyright (C) 2004, 2005 Metaha.
4 | *
5 | * TrimPath Template is licensed under the GNU General Public License
6 | * and the Apache License, Version 2.0, as follows:
7 | *
8 | * This program is free software; you can redistribute it and/or
9 | * modify it under the terms of the GNU General Public License
10 | * as published by the Free Software Foundation; either version 2
11 | * of the License, or (at your option) any later version.
12 | *
13 | * This program is distributed WITHOUT ANY WARRANTY; without even the
14 | * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 | * See the GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * along with this program; if not, write to the Free Software
19 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 | *
21 | * Licensed under the Apache License, Version 2.0 (the "License");
22 | * you may not use this file except in compliance with the License.
23 | * You may obtain a copy of the License at
24 | *
25 | * http://www.apache.org/licenses/LICENSE-2.0
26 | *
27 | * Unless required by applicable law or agreed to in writing, software
28 | * distributed under the License is distributed on an "AS IS" BASIS,
29 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30 | * See the License for the specific language governing permissions and
31 | * limitations under the License.
32 | */
33 | var TrimPath;
34 |
35 | // TODO: Debugging mode vs stop-on-error mode - runtime flag.
36 | // TODO: Handle || (or) characters and backslashes.
37 | // TODO: Add more modifiers.
38 |
39 | (function() { // Using a closure to keep global namespace clean.
40 | if (TrimPath == null)
41 | TrimPath = new Object();
42 | if (TrimPath.evalEx == null)
43 | TrimPath.evalEx = function(src) { return eval(src); };
44 |
45 | var UNDEFINED;
46 | if (Array.prototype.pop == null) // IE 5.x fix from Igor Poteryaev.
47 | Array.prototype.pop = function() {
48 | if (this.length === 0) {return UNDEFINED;}
49 | return this[--this.length];
50 | };
51 | if (Array.prototype.push == null) // IE 5.x fix from Igor Poteryaev.
52 | Array.prototype.push = function() {
53 | for (var i = 0; i < arguments.length; ++i) {this[this.length] = arguments[i];}
54 | return this.length;
55 | };
56 |
57 | TrimPath.parseTemplate = function(tmplContent, optTmplName, optEtc) {
58 | if (optEtc == null)
59 | optEtc = TrimPath.parseTemplate_etc;
60 | var funcSrc = parse(tmplContent, optTmplName, optEtc);
61 | var func = TrimPath.evalEx(funcSrc, optTmplName, 1);
62 | if (func != null)
63 | return new optEtc.Template(optTmplName, tmplContent, funcSrc, func, optEtc);
64 | return null;
65 | }
66 |
67 | try {
68 | String.prototype.process = function(context, optFlags) {
69 | var template = TrimPath.parseTemplate(this, null);
70 | if (template != null)
71 | return template.process(context, optFlags);
72 | return this;
73 | }
74 | } catch (e) { // Swallow exception, such as when String.prototype is sealed.
75 | }
76 |
77 | TrimPath.parseTemplate_etc = {}; // Exposed for extensibility.
78 | TrimPath.parseTemplate_etc.statementTag = "forelse|for|if|elseif|else|var|macro";
79 | TrimPath.parseTemplate_etc.statementDef = { // Lookup table for statement tags.
80 | "if" : { delta: 1, prefix: "if (", suffix: ") {", paramMin: 1 },
81 | "else" : { delta: 0, prefix: "} else {" },
82 | "elseif" : { delta: 0, prefix: "} else if (", suffix: ") {", paramDefault: "true" },
83 | "/if" : { delta: -1, prefix: "}" },
84 | "for" : { delta: 1, paramMin: 3,
85 | prefixFunc : function(stmtParts, state, tmplName, etc) {
86 | if (stmtParts[2] != "in")
87 | throw new etc.ParseError(tmplName, state.line, "bad for loop statement: " + stmtParts.join(' '));
88 | var iterVar = stmtParts[1];
89 | var listVar = "__LIST__" + iterVar;
90 | return [ "var ", listVar, " = ", stmtParts[3], ";",
91 | // Fix from Ross Shaull for hash looping, make sure that we have an array of loop lengths to treat like a stack.
92 | "var __LENGTH_STACK__;",
93 | "if (typeof(__LENGTH_STACK__) == 'undefined' || !__LENGTH_STACK__.length) __LENGTH_STACK__ = new Array();",
94 | "__LENGTH_STACK__[__LENGTH_STACK__.length] = 0;", // Push a new for-loop onto the stack of loop lengths.
95 | "if ((", listVar, ") != null) { ",
96 | "var ", iterVar, "_ct = 0;", // iterVar_ct variable, added by B. Bittman
97 | "for (var ", iterVar, "_index in ", listVar, ") { ",
98 | iterVar, "_ct++;",
99 | "if (typeof(", listVar, "[", iterVar, "_index]) == 'function') {continue;}", // IE 5.x fix from Igor Poteryaev.
100 | "__LENGTH_STACK__[__LENGTH_STACK__.length - 1]++;",
101 | "var ", iterVar, " = ", listVar, "[", iterVar, "_index];" ].join("");
102 | } },
103 | "forelse" : { delta: 0, prefix: "} } if (__LENGTH_STACK__[__LENGTH_STACK__.length - 1] == 0) { if (", suffix: ") {", paramDefault: "true" },
104 | "/for" : { delta: -1, prefix: "} }; delete __LENGTH_STACK__[__LENGTH_STACK__.length - 1];" }, // Remove the just-finished for-loop from the stack of loop lengths.
105 | "var" : { delta: 0, prefix: "var ", suffix: ";" },
106 | "macro" : { delta: 1,
107 | prefixFunc : function(stmtParts, state, tmplName, etc) {
108 | var macroName = stmtParts[1].split('(')[0];
109 | return [ "var ", macroName, " = function",
110 | stmtParts.slice(1).join(' ').substring(macroName.length),
111 | "{ var _OUT_arr = []; var _OUT = { write: function(m) { if (m) _OUT_arr.push(m); } }; " ].join('');
112 | } },
113 | "/macro" : { delta: -1, prefix: " return _OUT_arr.join(''); };" }
114 | }
115 | TrimPath.parseTemplate_etc.modifierDef = {
116 | "eat" : function(v) { return ""; },
117 | "escape" : function(s) { return String(s).replace(/&/g, "&").replace(//g, ">"); },
118 | "capitalize" : function(s) { return String(s).toUpperCase(); },
119 | "default" : function(s, d) { return s != null ? s : d; }
120 | }
121 | TrimPath.parseTemplate_etc.modifierDef.h = TrimPath.parseTemplate_etc.modifierDef.escape;
122 |
123 | TrimPath.parseTemplate_etc.Template = function(tmplName, tmplContent, funcSrc, func, etc) {
124 | this.process = function(context, flags) {
125 | if (context == null)
126 | context = {};
127 | if (context._MODIFIERS == null)
128 | context._MODIFIERS = {};
129 | if (context.defined == null)
130 | context.defined = function(str) { return (context[str] != undefined); };
131 | for (var k in etc.modifierDef) {
132 | if (context._MODIFIERS[k] == null)
133 | context._MODIFIERS[k] = etc.modifierDef[k];
134 | }
135 | for (var k in TrimPath.global_modifiers) {
136 | if (context._MODIFIERS[k] == null)
137 | context._MODIFIERS[k] = TrimPath.global_modifiers[k];
138 | }
139 | if (flags == null)
140 | flags = {};
141 | var resultArr = [];
142 | var resultOut = { write: function(m) { resultArr.push(m); } };
143 | try {
144 | func(resultOut, context, flags);
145 | } catch (e) {
146 | if (flags.throwExceptions == true)
147 | throw e;
148 | var result = new String(resultArr.join("") + "[ERROR: " + e.toString() + (e.message ? '; ' + e.message : '') + "]");
149 | result["exception"] = e;
150 | return result;
151 | }
152 | return resultArr.join("");
153 | }
154 | this.name = tmplName;
155 | this.source = tmplContent;
156 | this.sourceFunc = funcSrc;
157 | this.toString = function() { return "TrimPath.Template [" + tmplName + "]"; }
158 | }
159 | TrimPath.parseTemplate_etc.ParseError = function(name, line, message) {
160 | this.name = name;
161 | this.line = line;
162 | this.message = message;
163 | }
164 | TrimPath.parseTemplate_etc.ParseError.prototype.toString = function() {
165 | return ("TrimPath template ParseError in " + this.name + ": line " + this.line + ", " + this.message);
166 | }
167 |
168 | var parse = function(body, tmplName, etc) {
169 | body = cleanWhiteSpace(body);
170 | var funcText = [ "var TrimPath_Template_TEMP = function(_OUT, _CONTEXT, _FLAGS) { with (_CONTEXT) {" ];
171 | var state = { stack: [], line: 1 }; // TODO: Fix line number counting.
172 | var endStmtPrev = -1;
173 | while (endStmtPrev + 1 < body.length) {
174 | var begStmt = endStmtPrev;
175 | // Scan until we find some statement markup.
176 | begStmt = body.indexOf("{", begStmt + 1);
177 | while (begStmt >= 0) {
178 | var endStmt = body.indexOf('}', begStmt + 1);
179 | var stmt = body.substring(begStmt, endStmt);
180 | var blockrx = stmt.match(/^\{(cdata|minify|eval)/); // From B. Bittman, minify/eval/cdata implementation.
181 | if (blockrx) {
182 | var blockType = blockrx[1];
183 | var blockMarkerBeg = begStmt + blockType.length + 1;
184 | var blockMarkerEnd = body.indexOf('}', blockMarkerBeg);
185 | if (blockMarkerEnd >= 0) {
186 | var blockMarker;
187 | if( blockMarkerEnd - blockMarkerBeg <= 0 ) {
188 | blockMarker = "{/" + blockType + "}";
189 | } else {
190 | blockMarker = body.substring(blockMarkerBeg + 1, blockMarkerEnd);
191 | }
192 |
193 | var blockEnd = body.indexOf(blockMarker, blockMarkerEnd + 1);
194 | if (blockEnd >= 0) {
195 | emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText);
196 |
197 | var blockText = body.substring(blockMarkerEnd + 1, blockEnd);
198 | if (blockType == 'cdata') {
199 | emitText(blockText, funcText);
200 | } else if (blockType == 'minify') {
201 | emitText(scrubWhiteSpace(blockText), funcText);
202 | } else if (blockType == 'eval') {
203 | if (blockText != null && blockText.length > 0) // From B. Bittman, eval should not execute until process().
204 | funcText.push('_OUT.write( (function() { ' + blockText + ' })() );');
205 | }
206 | begStmt = endStmtPrev = blockEnd + blockMarker.length - 1;
207 | }
208 | }
209 | } else if (body.charAt(begStmt - 1) != '$' && // Not an expression or backslashed,
210 | body.charAt(begStmt - 1) != '\\') { // so check if it is a statement tag.
211 | var offset = (body.charAt(begStmt + 1) == '/' ? 2 : 1); // Close tags offset of 2 skips '/'.
212 | // 10 is larger than maximum statement tag length.
213 | if (body.substring(begStmt + offset, begStmt + 10 + offset).search(TrimPath.parseTemplate_etc.statementTag) == 0)
214 | break; // Found a match.
215 | }
216 | begStmt = body.indexOf("{", begStmt + 1);
217 | }
218 | if (begStmt < 0) // In "a{for}c", begStmt will be 1.
219 | break;
220 | var endStmt = body.indexOf("}", begStmt + 1); // In "a{for}c", endStmt will be 5.
221 | if (endStmt < 0)
222 | break;
223 | emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText);
224 | emitStatement(body.substring(begStmt, endStmt + 1), state, funcText, tmplName, etc);
225 | endStmtPrev = endStmt;
226 | }
227 | emitSectionText(body.substring(endStmtPrev + 1), funcText);
228 | if (state.stack.length != 0)
229 | throw new etc.ParseError(tmplName, state.line, "unclosed, unmatched statement(s): " + state.stack.join(","));
230 | funcText.push("}}; TrimPath_Template_TEMP");
231 | return funcText.join("");
232 | }
233 |
234 | var emitStatement = function(stmtStr, state, funcText, tmplName, etc) {
235 | var parts = stmtStr.slice(1, -1).split(' ');
236 | var stmt = etc.statementDef[parts[0]]; // Here, parts[0] == for/if/else/...
237 | if (stmt == null) { // Not a real statement.
238 | emitSectionText(stmtStr, funcText);
239 | return;
240 | }
241 | if (stmt.delta < 0) {
242 | if (state.stack.length <= 0)
243 | throw new etc.ParseError(tmplName, state.line, "close tag does not match any previous statement: " + stmtStr);
244 | state.stack.pop();
245 | }
246 | if (stmt.delta > 0)
247 | state.stack.push(stmtStr);
248 |
249 | if (stmt.paramMin != null &&
250 | stmt.paramMin >= parts.length)
251 | throw new etc.ParseError(tmplName, state.line, "statement needs more parameters: " + stmtStr);
252 | if (stmt.prefixFunc != null)
253 | funcText.push(stmt.prefixFunc(parts, state, tmplName, etc));
254 | else
255 | funcText.push(stmt.prefix);
256 | if (stmt.suffix != null) {
257 | if (parts.length <= 1) {
258 | if (stmt.paramDefault != null)
259 | funcText.push(stmt.paramDefault);
260 | } else {
261 | for (var i = 1; i < parts.length; i++) {
262 | if (i > 1)
263 | funcText.push(' ');
264 | funcText.push(parts[i]);
265 | }
266 | }
267 | funcText.push(stmt.suffix);
268 | }
269 | }
270 |
271 | var emitSectionText = function(text, funcText) {
272 | if (text.length <= 0)
273 | return;
274 | var nlPrefix = 0; // Index to first non-newline in prefix.
275 | var nlSuffix = text.length - 1; // Index to first non-space/tab in suffix.
276 | while (nlPrefix < text.length && (text.charAt(nlPrefix) == '\n'))
277 | nlPrefix++;
278 | while (nlSuffix >= 0 && (text.charAt(nlSuffix) == ' ' || text.charAt(nlSuffix) == '\t'))
279 | nlSuffix--;
280 | if (nlSuffix < nlPrefix)
281 | nlSuffix = nlPrefix;
282 | if (nlPrefix > 0) {
283 | funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("');
284 | var s = text.substring(0, nlPrefix).replace('\n', '\\n'); // A macro IE fix from BJessen.
285 | if (s.charAt(s.length - 1) == '\n')
286 | s = s.substring(0, s.length - 1);
287 | funcText.push(s);
288 | funcText.push('");');
289 | }
290 | var lines = text.substring(nlPrefix, nlSuffix + 1).split('\n');
291 | for (var i = 0; i < lines.length; i++) {
292 | emitSectionTextLine(lines[i], funcText);
293 | if (i < lines.length - 1)
294 | funcText.push('_OUT.write("\\n");\n');
295 | }
296 | if (nlSuffix + 1 < text.length) {
297 | funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("');
298 | var s = text.substring(nlSuffix + 1).replace('\n', '\\n');
299 | if (s.charAt(s.length - 1) == '\n')
300 | s = s.substring(0, s.length - 1);
301 | funcText.push(s);
302 | funcText.push('");');
303 | }
304 | }
305 |
306 | var emitSectionTextLine = function(line, funcText) {
307 | var endMarkPrev = '}';
308 | var endExprPrev = -1;
309 | while (endExprPrev + endMarkPrev.length < line.length) {
310 | var begMark = "${", endMark = "}";
311 | var begExpr = line.indexOf(begMark, endExprPrev + endMarkPrev.length); // In "a${b}c", begExpr == 1
312 | if (begExpr < 0)
313 | break;
314 | if (line.charAt(begExpr + 2) == '%') {
315 | begMark = "${%";
316 | endMark = "%}";
317 | }
318 | var endExpr = line.indexOf(endMark, begExpr + begMark.length); // In "a${b}c", endExpr == 4;
319 | if (endExpr < 0)
320 | break;
321 | emitText(line.substring(endExprPrev + endMarkPrev.length, begExpr), funcText);
322 | // Example: exprs == 'firstName|default:"John Doe"|capitalize'.split('|')
323 | var exprArr = line.substring(begExpr + begMark.length, endExpr).replace(/\|\|/g, "#@@#").split('|');
324 | for (var k in exprArr) {
325 | if (exprArr[k].replace) // IE 5.x fix from Igor Poteryaev.
326 | exprArr[k] = exprArr[k].replace(/#@@#/g, '||');
327 | }
328 | funcText.push('_OUT.write(');
329 | emitExpression(exprArr, exprArr.length - 1, funcText);
330 | funcText.push(');');
331 | endExprPrev = endExpr;
332 | endMarkPrev = endMark;
333 | }
334 | emitText(line.substring(endExprPrev + endMarkPrev.length), funcText);
335 | }
336 |
337 | var emitText = function(text, funcText) {
338 | if (text == null ||
339 | text.length <= 0)
340 | return;
341 | text = text.replace(/\\/g, '\\\\');
342 | text = text.replace(/\n/g, '\\n');
343 | text = text.replace(/"/g, '\\"');
344 | funcText.push('_OUT.write("');
345 | funcText.push(text);
346 | funcText.push('");');
347 | }
348 |
349 | var emitExpression = function(exprArr, index, funcText) {
350 | // Ex: foo|a:x|b:y1,y2|c:z1,z2 is emitted as c(b(a(foo,x),y1,y2),z1,z2)
351 | var expr = exprArr[index]; // Ex: exprArr == [firstName,capitalize,default:"John Doe"]
352 | if (index <= 0) { // Ex: expr == 'default:"John Doe"'
353 | funcText.push(expr);
354 | return;
355 | }
356 | var parts = expr.split(':');
357 | funcText.push('_MODIFIERS["');
358 | funcText.push(parts[0]); // The parts[0] is a modifier function name, like capitalize.
359 | funcText.push('"](');
360 | emitExpression(exprArr, index - 1, funcText);
361 | if (parts.length > 1) {
362 | funcText.push(',');
363 | funcText.push(parts[1]);
364 | }
365 | funcText.push(')');
366 | }
367 |
368 | var cleanWhiteSpace = function(result) {
369 | result = result.replace(/\t/g, " ");
370 | result = result.replace(/\r\n/g, "\n");
371 | result = result.replace(/\r/g, "\n");
372 | result = result.replace(/^(\s*\S*(\s+\S+)*)\s*$/, '$1'); // Right trim by Igor Poteryaev.
373 | return result;
374 | }
375 |
376 | var scrubWhiteSpace = function(result) {
377 | result = result.replace(/^\s+/g, "");
378 | result = result.replace(/\s+$/g, "");
379 | result = result.replace(/\s+/g, " ");
380 | result = result.replace(/^(\s*\S*(\s+\S+)*)\s*$/, '$1'); // Right trim by Igor Poteryaev.
381 | return result;
382 | }
383 |
384 | // The DOM helper functions depend on DOM/DHTML, so they only work in a browser.
385 | // However, these are not considered core to the engine.
386 | //
387 | TrimPath.parseDOMTemplate = function(elementId, optDocument, optEtc) {
388 | if (optDocument == null)
389 | optDocument = document;
390 | var element = optDocument.getElementById(elementId);
391 | var content = element.value; // Like textarea.value.
392 | if (content == null)
393 | content = element.innerHTML; // Like textarea.innerHTML.
394 | content = content.replace(/</g, "<").replace(/>/g, ">");
395 | return TrimPath.parseTemplate(content, elementId, optEtc);
396 | }
397 |
398 | TrimPath.processDOMTemplate = function(elementId, context, optFlags, optDocument, optEtc) {
399 | return TrimPath.parseDOMTemplate(elementId, optDocument, optEtc).process(context, optFlags);
400 | }
401 | TrimPath.global_modifiers={};
402 | }) ();
403 |
--------------------------------------------------------------------------------
/bin/ui.core.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery UI 1.6rc6
3 | *
4 | * Copyright (c) 2009 AUTHORS.txt (http://ui.jquery.com/about)
5 | * Dual licensed under the MIT (MIT-LICENSE.txt)
6 | * and GPL (GPL-LICENSE.txt) licenses.
7 | *
8 | * http://docs.jquery.com/UI
9 | */
10 | ;(function($) {
11 |
12 | var _remove = $.fn.remove,
13 | isFF2 = $.browser.mozilla && (parseFloat($.browser.version) < 1.9);
14 |
15 | //Helper functions and ui object
16 | $.ui = {
17 | version: "1.6rc6",
18 |
19 | // $.ui.plugin is deprecated. Use the proxy pattern instead.
20 | plugin: {
21 | add: function(module, option, set) {
22 | var proto = $.ui[module].prototype;
23 | for(var i in set) {
24 | proto.plugins[i] = proto.plugins[i] || [];
25 | proto.plugins[i].push([option, set[i]]);
26 | }
27 | },
28 | call: function(instance, name, args) {
29 | var set = instance.plugins[name];
30 | if(!set) { return; }
31 |
32 | for (var i = 0; i < set.length; i++) {
33 | if (instance.options[set[i][0]]) {
34 | set[i][1].apply(instance.element, args);
35 | }
36 | }
37 | }
38 | },
39 |
40 | contains: function(a, b) {
41 | return document.compareDocumentPosition
42 | ? a.compareDocumentPosition(b) & 16
43 | : a !== b && a.contains(b);
44 | },
45 |
46 | cssCache: {},
47 | css: function(name) {
48 | if ($.ui.cssCache[name]) { return $.ui.cssCache[name]; }
49 | var tmp = $('
').addClass(name).css({position:'absolute', top:'-5000px', left:'-5000px', display:'block'}).appendTo('body');
50 |
51 | //if (!$.browser.safari)
52 | //tmp.appendTo('body');
53 |
54 | //Opera and Safari set width and height to 0px instead of auto
55 | //Safari returns rgba(0,0,0,0) when bgcolor is not set
56 | $.ui.cssCache[name] = !!(
57 | (!(/auto|default/).test(tmp.css('cursor')) || (/^[1-9]/).test(tmp.css('height')) || (/^[1-9]/).test(tmp.css('width')) ||
58 | !(/none/).test(tmp.css('backgroundImage')) || !(/transparent|rgba\(0, 0, 0, 0\)/).test(tmp.css('backgroundColor')))
59 | );
60 | try { $('body').get(0).removeChild(tmp.get(0)); } catch(e){}
61 | return $.ui.cssCache[name];
62 | },
63 |
64 | hasScroll: function(el, a) {
65 |
66 | //If overflow is hidden, the element might have extra content, but the user wants to hide it
67 | if ($(el).css('overflow') == 'hidden') { return false; }
68 |
69 | var scroll = (a && a == 'left') ? 'scrollLeft' : 'scrollTop',
70 | has = false;
71 |
72 | if (el[scroll] > 0) { return true; }
73 |
74 | // TODO: determine which cases actually cause this to happen
75 | // if the element doesn't have the scroll set, see if it's possible to
76 | // set the scroll
77 | el[scroll] = 1;
78 | has = (el[scroll] > 0);
79 | el[scroll] = 0;
80 | return has;
81 | },
82 |
83 | isOverAxis: function(x, reference, size) {
84 | //Determines when x coordinate is over "b" element axis
85 | return (x > reference) && (x < (reference + size));
86 | },
87 |
88 | isOver: function(y, x, top, left, height, width) {
89 | //Determines when x, y coordinates is over "b" element
90 | return $.ui.isOverAxis(y, top, height) && $.ui.isOverAxis(x, left, width);
91 | },
92 |
93 | keyCode: {
94 | BACKSPACE: 8,
95 | CAPS_LOCK: 20,
96 | COMMA: 188,
97 | CONTROL: 17,
98 | DELETE: 46,
99 | DOWN: 40,
100 | END: 35,
101 | ENTER: 13,
102 | ESCAPE: 27,
103 | HOME: 36,
104 | INSERT: 45,
105 | LEFT: 37,
106 | NUMPAD_ADD: 107,
107 | NUMPAD_DECIMAL: 110,
108 | NUMPAD_DIVIDE: 111,
109 | NUMPAD_ENTER: 108,
110 | NUMPAD_MULTIPLY: 106,
111 | NUMPAD_SUBTRACT: 109,
112 | PAGE_DOWN: 34,
113 | PAGE_UP: 33,
114 | PERIOD: 190,
115 | RIGHT: 39,
116 | SHIFT: 16,
117 | SPACE: 32,
118 | TAB: 9,
119 | UP: 38
120 | }
121 | };
122 |
123 | // WAI-ARIA normalization
124 | if (isFF2) {
125 | var attr = $.attr,
126 | removeAttr = $.fn.removeAttr,
127 | ariaNS = "http://www.w3.org/2005/07/aaa",
128 | ariaState = /^aria-/,
129 | ariaRole = /^wairole:/;
130 |
131 | $.attr = function(elem, name, value) {
132 | var set = value !== undefined;
133 |
134 | return (name == 'role'
135 | ? (set
136 | ? attr.call(this, elem, name, "wairole:" + value)
137 | : (attr.apply(this, arguments) || "").replace(ariaRole, ""))
138 | : (ariaState.test(name)
139 | ? (set
140 | ? elem.setAttributeNS(ariaNS,
141 | name.replace(ariaState, "aaa:"), value)
142 | : attr.call(this, elem, name.replace(ariaState, "aaa:")))
143 | : attr.apply(this, arguments)));
144 | };
145 |
146 | $.fn.removeAttr = function(name) {
147 | return (ariaState.test(name)
148 | ? this.each(function() {
149 | this.removeAttributeNS(ariaNS, name.replace(ariaState, ""));
150 | }) : removeAttr.call(this, name));
151 | };
152 | }
153 |
154 | //jQuery plugins
155 | $.fn.extend({
156 | remove: function() {
157 | // Safari has a native remove event which actually removes DOM elements,
158 | // so we have to use triggerHandler instead of trigger (#3037).
159 | $("*", this).add(this).each(function() {
160 | $(this).triggerHandler("remove");
161 | });
162 | return _remove.apply(this, arguments );
163 | },
164 |
165 | enableSelection: function() {
166 | return this
167 | .attr('unselectable', 'off')
168 | .css('MozUserSelect', '')
169 | .unbind('selectstart.ui');
170 | },
171 |
172 | disableSelection: function() {
173 | return this
174 | .attr('unselectable', 'on')
175 | .css('MozUserSelect', 'none')
176 | .bind('selectstart.ui', function() { return false; });
177 | },
178 |
179 | scrollParent: function() {
180 | var scrollParent;
181 | if(($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
182 | scrollParent = this.parents().filter(function() {
183 | return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1)) && (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
184 | }).eq(0);
185 | } else {
186 | scrollParent = this.parents().filter(function() {
187 | return (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
188 | }).eq(0);
189 | }
190 |
191 | return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
192 | }
193 | });
194 |
195 |
196 | //Additional selectors
197 | $.extend($.expr[':'], {
198 | data: function(elem, i, match) {
199 | return !!$.data(elem, match[3]);
200 | },
201 |
202 | focusable: function(element) {
203 | var nodeName = element.nodeName.toLowerCase(),
204 | tabIndex = $.attr(element, 'tabindex');
205 | return (/input|select|textarea|button|object/.test(nodeName)
206 | ? !element.disabled
207 | : 'a' == nodeName || 'area' == nodeName
208 | ? element.href || !isNaN(tabIndex)
209 | : !isNaN(tabIndex))
210 | // the element and all of its ancestors must be visible
211 | // the browser may report that the area is hidden
212 | && !$(element)['area' == nodeName ? 'parents' : 'closest'](':hidden').length;
213 | },
214 |
215 | tabbable: function(element) {
216 | var tabIndex = $.attr(element, 'tabindex');
217 | return (isNaN(tabIndex) || tabIndex >= 0) && $(element).is(':focusable');
218 | }
219 | });
220 |
221 |
222 | // $.widget is a factory to create jQuery plugins
223 | // taking some boilerplate code out of the plugin code
224 | function getter(namespace, plugin, method, args) {
225 | function getMethods(type) {
226 | var methods = $[namespace][plugin][type] || [];
227 | return (typeof methods == 'string' ? methods.split(/,?\s+/) : methods);
228 | }
229 |
230 | var methods = getMethods('getter');
231 | if (args.length == 1 && typeof args[0] == 'string') {
232 | methods = methods.concat(getMethods('getterSetter'));
233 | }
234 | return ($.inArray(method, methods) != -1);
235 | }
236 |
237 | $.widget = function(name, prototype) {
238 | var namespace = name.split(".")[0];
239 | name = name.split(".")[1];
240 |
241 | // create plugin method
242 | $.fn[name] = function(options) {
243 | var isMethodCall = (typeof options == 'string'),
244 | args = Array.prototype.slice.call(arguments, 1);
245 |
246 | // prevent calls to internal methods
247 | if (isMethodCall && options.substring(0, 1) == '_') {
248 | return this;
249 | }
250 |
251 | // handle getter methods
252 | if (isMethodCall && getter(namespace, name, options, args)) {
253 | var instance = $.data(this[0], name);
254 | return (instance ? instance[options].apply(instance, args)
255 | : undefined);
256 | }
257 |
258 | // handle initialization and non-getter methods
259 | return this.each(function() {
260 | var instance = $.data(this, name);
261 |
262 | // constructor
263 | (!instance && !isMethodCall &&
264 | $.data(this, name, new $[namespace][name](this, options))._init());
265 |
266 | // method call
267 | (instance && isMethodCall && $.isFunction(instance[options]) &&
268 | instance[options].apply(instance, args));
269 | });
270 | };
271 |
272 | // create widget constructor
273 | $[namespace] = $[namespace] || {};
274 | $[namespace][name] = function(element, options) {
275 | var self = this;
276 |
277 | this.namespace = namespace;
278 | this.widgetName = name;
279 | this.widgetEventPrefix = $[namespace][name].eventPrefix || name;
280 | this.widgetBaseClass = namespace + '-' + name;
281 |
282 | this.options = $.extend({},
283 | $.widget.defaults,
284 | $[namespace][name].defaults,
285 | $.metadata && $.metadata.get(element)[name],
286 | options);
287 |
288 | this.element = $(element)
289 | .bind('setData.' + name, function(event, key, value) {
290 | if (event.target == element) {
291 | return self._setData(key, value);
292 | }
293 | })
294 | .bind('getData.' + name, function(event, key) {
295 | if (event.target == element) {
296 | return self._getData(key);
297 | }
298 | })
299 | .bind('remove', function() {
300 | return self.destroy();
301 | });
302 | };
303 |
304 | // add widget prototype
305 | $[namespace][name].prototype = $.extend({}, $.widget.prototype, prototype);
306 |
307 | // TODO: merge getter and getterSetter properties from widget prototype
308 | // and plugin prototype
309 | $[namespace][name].getterSetter = 'option';
310 | };
311 |
312 | $.widget.prototype = {
313 | _init: function() {},
314 | destroy: function() {
315 | this.element.removeData(this.widgetName)
316 | .removeClass(this.widgetBaseClass + '-disabled' + ' ' + this.namespace + '-state-disabled')
317 | .removeAttr('aria-disabled');
318 | },
319 |
320 | option: function(key, value) {
321 | var options = key,
322 | self = this;
323 |
324 | if (typeof key == "string") {
325 | if (value === undefined) {
326 | return this._getData(key);
327 | }
328 | options = {};
329 | options[key] = value;
330 | }
331 |
332 | $.each(options, function(key, value) {
333 | self._setData(key, value);
334 | });
335 | },
336 | _getData: function(key) {
337 | return this.options[key];
338 | },
339 | _setData: function(key, value) {
340 | this.options[key] = value;
341 |
342 | if (key == 'disabled') {
343 | this.element
344 | [value ? 'addClass' : 'removeClass'](
345 | this.widgetBaseClass + '-disabled' + ' ' +
346 | this.namespace + '-state-disabled')
347 | .attr("aria-disabled", value);
348 | }
349 | },
350 |
351 | enable: function() {
352 | this._setData('disabled', false);
353 | },
354 | disable: function() {
355 | this._setData('disabled', true);
356 | },
357 |
358 | _trigger: function(type, event, data) {
359 | var callback = this.options[type],
360 | eventName = (type == this.widgetEventPrefix
361 | ? type : this.widgetEventPrefix + type);
362 |
363 | event = $.Event(event);
364 | event.type = eventName;
365 |
366 | // copy original event properties over to the new event
367 | // this would happen if we could call $.event.fix instead of $.Event
368 | // but we don't have a way to force an event to be fixed multiple times
369 | if (event.originalEvent) {
370 | for (var i = $.event.props.length, prop; i;) {
371 | prop = $.event.props[--i];
372 | event[prop] = event.originalEvent[prop];
373 | }
374 | }
375 |
376 | this.element.trigger(event, data);
377 |
378 | return !($.isFunction(callback) && callback.call(this.element[0], event, data) === false
379 | || event.isDefaultPrevented());
380 | }
381 | };
382 |
383 | $.widget.defaults = {
384 | disabled: false
385 | };
386 |
387 |
388 | /** Mouse Interaction Plugin **/
389 |
390 | $.ui.mouse = {
391 | _mouseInit: function() {
392 | var self = this;
393 |
394 | this.element
395 | .bind('mousedown.'+this.widgetName, function(event) {
396 | return self._mouseDown(event);
397 | })
398 | .bind('click.'+this.widgetName, function(event) {
399 | if(self._preventClickEvent) {
400 | self._preventClickEvent = false;
401 | return false;
402 | }
403 | });
404 |
405 | // Prevent text selection in IE
406 | if ($.browser.msie) {
407 | this._mouseUnselectable = this.element.attr('unselectable');
408 | this.element.attr('unselectable', 'on');
409 | }
410 |
411 | this.started = false;
412 | },
413 |
414 | // TODO: make sure destroying one instance of mouse doesn't mess with
415 | // other instances of mouse
416 | _mouseDestroy: function() {
417 | this.element.unbind('.'+this.widgetName);
418 |
419 | // Restore text selection in IE
420 | ($.browser.msie
421 | && this.element.attr('unselectable', this._mouseUnselectable));
422 | },
423 |
424 | _mouseDown: function(event) {
425 | // don't let more than one widget handle mouseStart
426 | if (event.originalEvent.mouseHandled) { return; }
427 |
428 | // we may have missed mouseup (out of window)
429 | (this._mouseStarted && this._mouseUp(event));
430 |
431 | this._mouseDownEvent = event;
432 |
433 | var self = this,
434 | btnIsLeft = (event.which == 1),
435 | elIsCancel = (typeof this.options.cancel == "string" ? $(event.target).parents().add(event.target).filter(this.options.cancel).length : false);
436 | if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
437 | return true;
438 | }
439 |
440 | this.mouseDelayMet = !this.options.delay;
441 | if (!this.mouseDelayMet) {
442 | this._mouseDelayTimer = setTimeout(function() {
443 | self.mouseDelayMet = true;
444 | }, this.options.delay);
445 | }
446 |
447 | if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
448 | this._mouseStarted = (this._mouseStart(event) !== false);
449 | if (!this._mouseStarted) {
450 | event.preventDefault();
451 | return true;
452 | }
453 | }
454 |
455 | // these delegates are required to keep context
456 | this._mouseMoveDelegate = function(event) {
457 | return self._mouseMove(event);
458 | };
459 | this._mouseUpDelegate = function(event) {
460 | return self._mouseUp(event);
461 | };
462 | $(document)
463 | .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
464 | .bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
465 |
466 | // preventDefault() is used to prevent the selection of text here -
467 | // however, in Safari, this causes select boxes not to be selectable
468 | // anymore, so this fix is needed
469 | ($.browser.safari || event.preventDefault());
470 |
471 | event.originalEvent.mouseHandled = true;
472 | return true;
473 | },
474 |
475 | _mouseMove: function(event) {
476 | // IE mouseup check - mouseup happened when mouse was out of window
477 | if ($.browser.msie && !event.button) {
478 | return this._mouseUp(event);
479 | }
480 |
481 | if (this._mouseStarted) {
482 | this._mouseDrag(event);
483 | return event.preventDefault();
484 | }
485 |
486 | if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
487 | this._mouseStarted =
488 | (this._mouseStart(this._mouseDownEvent, event) !== false);
489 | (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
490 | }
491 |
492 | return !this._mouseStarted;
493 | },
494 |
495 | _mouseUp: function(event) {
496 | $(document)
497 | .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
498 | .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
499 |
500 | if (this._mouseStarted) {
501 | this._mouseStarted = false;
502 | this._preventClickEvent = true;
503 | this._mouseStop(event);
504 | }
505 |
506 | return false;
507 | },
508 |
509 | _mouseDistanceMet: function(event) {
510 | return (Math.max(
511 | Math.abs(this._mouseDownEvent.pageX - event.pageX),
512 | Math.abs(this._mouseDownEvent.pageY - event.pageY)
513 | ) >= this.options.distance
514 | );
515 | },
516 |
517 | _mouseDelayMet: function(event) {
518 | return this.mouseDelayMet;
519 | },
520 |
521 | // These are placeholder methods, to be overriden by extending plugin
522 | _mouseStart: function(event) {},
523 | _mouseDrag: function(event) {},
524 | _mouseStop: function(event) {},
525 | _mouseCapture: function(event) { return true; }
526 | };
527 |
528 | $.ui.mouse.defaults = {
529 | cancel: null,
530 | distance: 1,
531 | delay: 0
532 | };
533 |
534 | })(jQuery);
535 |
--------------------------------------------------------------------------------
/bin/ui.sortable.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery UI Sortable 1.6rc6
3 | *
4 | * Copyright (c) 2009 AUTHORS.txt (http://ui.jquery.com/about)
5 | * Dual licensed under the MIT (MIT-LICENSE.txt)
6 | * and GPL (GPL-LICENSE.txt) licenses.
7 | *
8 | * http://docs.jquery.com/UI/Sortables
9 | *
10 | * Depends:
11 | * ui.core.js
12 | */
13 | (function($) {
14 |
15 | $.widget("ui.sortable", $.extend({}, $.ui.mouse, {
16 | _init: function() {
17 |
18 | var o = this.options;
19 | this.containerCache = {};
20 | (this.options.cssNamespace && this.element.addClass(this.options.cssNamespace+"-sortable"));
21 |
22 | //Get the items
23 | this.refresh();
24 |
25 | //Let's determine if the items are floating
26 | this.floating = this.items.length ? (/left|right/).test(this.items[0].item.css('float')) : false;
27 |
28 | //Let's determine the parent's offset
29 | this.offset = this.element.offset();
30 |
31 | //Initialize mouse events for interaction
32 | this._mouseInit();
33 |
34 | },
35 |
36 | destroy: function() {
37 | this.element
38 | .removeClass(this.options.cssNamespace+"-sortable "+this.options.cssNamespace+"-sortable-disabled")
39 | .removeData("sortable")
40 | .unbind(".sortable");
41 | this._mouseDestroy();
42 |
43 | for ( var i = this.items.length - 1; i >= 0; i-- )
44 | this.items[i].item.removeData("sortable-item");
45 | },
46 |
47 | _mouseCapture: function(event, overrideHandle) {
48 |
49 | if (this.reverting) {
50 | return false;
51 | }
52 |
53 | if(this.options.disabled || this.options.type == 'static') return false;
54 |
55 | //We have to refresh the items data once first
56 | this._refreshItems(event);
57 |
58 | //Find out if the clicked node (or one of its parents) is a actual item in this.items
59 | var currentItem = null, self = this, nodes = $(event.target).parents().each(function() {
60 | if($.data(this, 'sortable-item') == self) {
61 | currentItem = $(this);
62 | return false;
63 | }
64 | });
65 | if($.data(event.target, 'sortable-item') == self) currentItem = $(event.target);
66 |
67 | if(!currentItem) return false;
68 | if(this.options.handle && !overrideHandle) {
69 | var validHandle = false;
70 |
71 | $(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
72 | if(!validHandle) return false;
73 | }
74 |
75 | this.currentItem = currentItem;
76 | this._removeCurrentsFromItems();
77 | return true;
78 |
79 | },
80 |
81 | _mouseStart: function(event, overrideHandle, noActivation) {
82 |
83 | var o = this.options, self = this;
84 | this.currentContainer = this;
85 |
86 | //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
87 | this.refreshPositions();
88 |
89 | //Create and append the visible helper
90 | this.helper = this._createHelper(event);
91 |
92 | //Cache the helper size
93 | this._cacheHelperProportions();
94 |
95 | /*
96 | * - Position generation -
97 | * This block generates everything position related - it's the core of draggables.
98 | */
99 |
100 | //Cache the margins of the original element
101 | this._cacheMargins();
102 |
103 | //Get the next scrolling parent
104 | this.scrollParent = this.helper.scrollParent();
105 |
106 | //The element's absolute position on the page minus margins
107 | this.offset = this.currentItem.offset();
108 | this.offset = {
109 | top: this.offset.top - this.margins.top,
110 | left: this.offset.left - this.margins.left
111 | };
112 |
113 | // Only after we got the offset, we can change the helper's position to absolute
114 | // TODO: Still need to figure out a way to make relative sorting possible
115 | this.helper.css("position", "absolute");
116 | this.cssPosition = this.helper.css("position");
117 |
118 | $.extend(this.offset, {
119 | click: { //Where the click happened, relative to the element
120 | left: event.pageX - this.offset.left,
121 | top: event.pageY - this.offset.top
122 | },
123 | parent: this._getParentOffset(),
124 | relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
125 | });
126 |
127 | //Generate the original position
128 | this.originalPosition = this._generatePosition(event);
129 | this.originalPageX = event.pageX;
130 | this.originalPageY = event.pageY;
131 |
132 | //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
133 | if(o.cursorAt)
134 | this._adjustOffsetFromHelper(o.cursorAt);
135 |
136 | //Cache the former DOM position
137 | this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
138 |
139 | //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
140 | if(this.helper[0] != this.currentItem[0]) {
141 | this.currentItem.hide();
142 | }
143 |
144 | //Create the placeholder
145 | this._createPlaceholder();
146 |
147 | //Set a containment if given in the options
148 | if(o.containment)
149 | this._setContainment();
150 |
151 | if(o.cursor) { // cursor option
152 | if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
153 | $('body').css("cursor", o.cursor);
154 | }
155 |
156 | if(o.opacity) { // opacity option
157 | if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
158 | this.helper.css("opacity", o.opacity);
159 | }
160 |
161 | if(o.zIndex) { // zIndex option
162 | if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
163 | this.helper.css("zIndex", o.zIndex);
164 | }
165 |
166 | //Prepare scrolling
167 | if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
168 | this.overflowOffset = this.scrollParent.offset();
169 |
170 | //Call callbacks
171 | this._trigger("start", event, this._uiHash());
172 |
173 | //Recache the helper size
174 | if(!this._preserveHelperProportions)
175 | this._cacheHelperProportions();
176 |
177 |
178 | //Post 'activate' events to possible containers
179 | if(!noActivation) {
180 | for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._uiHash(this)); }
181 | }
182 |
183 | //Prepare possible droppables
184 | if($.ui.ddmanager)
185 | $.ui.ddmanager.current = this;
186 |
187 | if ($.ui.ddmanager && !o.dropBehaviour)
188 | $.ui.ddmanager.prepareOffsets(this, event);
189 |
190 | this.dragging = true;
191 |
192 | this.helper.addClass(o.cssNamespace+'-sortable-helper');
193 | this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
194 | return true;
195 |
196 | },
197 |
198 | _mouseDrag: function(event) {
199 |
200 | //Compute the helpers position
201 | this.position = this._generatePosition(event);
202 | this.positionAbs = this._convertPositionTo("absolute");
203 |
204 | if (!this.lastPositionAbs) {
205 | this.lastPositionAbs = this.positionAbs;
206 | }
207 |
208 | //Do scrolling
209 | if(this.options.scroll) {
210 | var o = this.options, scrolled = false;
211 | if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
212 |
213 | if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
214 | this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
215 | else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
216 | this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
217 |
218 | if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
219 | this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
220 | else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
221 | this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
222 |
223 | } else {
224 |
225 | if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
226 | scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
227 | else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
228 | scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
229 |
230 | if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
231 | scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
232 | else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
233 | scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
234 |
235 | }
236 |
237 | if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
238 | $.ui.ddmanager.prepareOffsets(this, event);
239 | }
240 |
241 | //Regenerate the absolute position used for position checks
242 | this.positionAbs = this._convertPositionTo("absolute");
243 |
244 | //Set the helper position
245 | if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
246 | if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
247 |
248 | //Rearrange
249 | for (var i = this.items.length - 1; i >= 0; i--) {
250 |
251 | //Cache variables and intersection, continue if no intersection
252 | var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
253 | if (!intersection) continue;
254 |
255 | if(itemElement != this.currentItem[0] //cannot intersect with itself
256 | && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
257 | && !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
258 | && (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)
259 | ) {
260 |
261 | this.direction = intersection == 1 ? "down" : "up";
262 |
263 | if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
264 | this.options.sortIndicator.call(this, event, item);
265 | } else {
266 | break;
267 | }
268 |
269 | this._trigger("change", event, this._uiHash());
270 | break;
271 | }
272 | }
273 |
274 | //Post events to containers
275 | this._contactContainers(event);
276 |
277 | //Interconnect with droppables
278 | if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
279 |
280 | //Call callbacks
281 | this._trigger('sort', event, this._uiHash());
282 |
283 | this.lastPositionAbs = this.positionAbs;
284 | return false;
285 |
286 | },
287 |
288 | _mouseStop: function(event, noPropagation) {
289 |
290 | if(!event) return;
291 |
292 | //If we are using droppables, inform the manager about the drop
293 | if ($.ui.ddmanager && !this.options.dropBehaviour)
294 | $.ui.ddmanager.drop(this, event);
295 |
296 | if(this.options.revert) {
297 | var self = this;
298 | var cur = self.placeholder.offset();
299 |
300 | self.reverting = true;
301 |
302 | $(this.helper).animate({
303 | left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
304 | top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
305 | }, parseInt(this.options.revert, 10) || 500, function() {
306 | self._clear(event);
307 | });
308 | } else {
309 | this._clear(event, noPropagation);
310 | }
311 |
312 | return false;
313 |
314 | },
315 |
316 | cancel: function() {
317 |
318 | var self = this;
319 |
320 | if(this.dragging) {
321 |
322 | this._mouseUp();
323 |
324 | if(this.options.helper == "original")
325 | this.currentItem.css(this._storedCSS).removeClass(this.options.cssNamespace+"-sortable-helper");
326 | else
327 | this.currentItem.show();
328 |
329 | //Post deactivating events to containers
330 | for (var i = this.containers.length - 1; i >= 0; i--){
331 | this.containers[i]._trigger("deactivate", null, self._uiHash(this));
332 | if(this.containers[i].containerCache.over) {
333 | this.containers[i]._trigger("out", null, self._uiHash(this));
334 | this.containers[i].containerCache.over = 0;
335 | }
336 | }
337 |
338 | }
339 |
340 | //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
341 | if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
342 | if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();
343 |
344 | $.extend(this, {
345 | helper: null,
346 | dragging: false,
347 | reverting: false,
348 | _noFinalSort: null
349 | });
350 |
351 | if(this.domPosition.prev) {
352 | $(this.domPosition.prev).after(this.currentItem);
353 | } else {
354 | $(this.domPosition.parent).prepend(this.currentItem);
355 | }
356 |
357 | return true;
358 |
359 | },
360 |
361 | serialize: function(o) {
362 |
363 | var items = this._getItemsAsjQuery(o && o.connected);
364 | var str = []; o = o || {};
365 |
366 | $(items).each(function() {
367 | var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
368 | if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
369 | });
370 |
371 | return str.join('&');
372 |
373 | },
374 |
375 | toArray: function(o) {
376 |
377 | var items = this._getItemsAsjQuery(o && o.connected);
378 | var ret = []; o = o || {};
379 |
380 | items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
381 | return ret;
382 |
383 | },
384 |
385 | /* Be careful with the following core functions */
386 | _intersectsWith: function(item) {
387 |
388 | var x1 = this.positionAbs.left,
389 | x2 = x1 + this.helperProportions.width,
390 | y1 = this.positionAbs.top,
391 | y2 = y1 + this.helperProportions.height;
392 |
393 | var l = item.left,
394 | r = l + item.width,
395 | t = item.top,
396 | b = t + item.height;
397 |
398 | var dyClick = this.offset.click.top,
399 | dxClick = this.offset.click.left;
400 |
401 | var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
402 |
403 | if( this.options.tolerance == "pointer"
404 | || this.options.forcePointerForContainers
405 | || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
406 | ) {
407 | return isOverElement;
408 | } else {
409 |
410 | return (l < x1 + (this.helperProportions.width / 2) // Right Half
411 | && x2 - (this.helperProportions.width / 2) < r // Left Half
412 | && t < y1 + (this.helperProportions.height / 2) // Bottom Half
413 | && y2 - (this.helperProportions.height / 2) < b ); // Top Half
414 |
415 | }
416 | },
417 |
418 | _intersectsWithPointer: function(item) {
419 |
420 | var isOverElementHeight = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
421 | isOverElementWidth = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
422 | isOverElement = isOverElementHeight && isOverElementWidth,
423 | verticalDirection = this._getDragVerticalDirection(),
424 | horizontalDirection = this._getDragHorizontalDirection();
425 |
426 | if (!isOverElement)
427 | return false;
428 |
429 | return this.floating ?
430 | ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
431 | : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );
432 |
433 | },
434 |
435 | _intersectsWithSides: function(item) {
436 |
437 | var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
438 | isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
439 | verticalDirection = this._getDragVerticalDirection(),
440 | horizontalDirection = this._getDragHorizontalDirection();
441 |
442 | if (this.floating && horizontalDirection) {
443 | return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
444 | } else {
445 | return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
446 | }
447 |
448 | },
449 |
450 | _getDragVerticalDirection: function() {
451 | var delta = this.positionAbs.top - this.lastPositionAbs.top;
452 | return delta != 0 && (delta > 0 ? "down" : "up");
453 | },
454 |
455 | _getDragHorizontalDirection: function() {
456 | var delta = this.positionAbs.left - this.lastPositionAbs.left;
457 | return delta != 0 && (delta > 0 ? "right" : "left");
458 | },
459 |
460 | refresh: function(event) {
461 | this._refreshItems(event);
462 | this.refreshPositions();
463 | },
464 |
465 | _getItemsAsjQuery: function(connected) {
466 |
467 | var self = this;
468 | var items = [];
469 | var queries = [];
470 |
471 | if(this.options.connectWith && connected) {
472 | var connectWith = this.options.connectWith.constructor == String ? [this.options.connectWith] : this.options.connectWith;
473 | for (var i = connectWith.length - 1; i >= 0; i--){
474 | var cur = $(connectWith[i]);
475 | for (var j = cur.length - 1; j >= 0; j--){
476 | var inst = $.data(cur[j], 'sortable');
477 | if(inst && inst != this && !inst.options.disabled) {
478 | queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not("."+inst.options.cssNamespace+"-sortable-helper"), inst]);
479 | }
480 | };
481 | };
482 | }
483 |
484 | queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not("."+this.options.cssNamespace+"-sortable-helper"), this]);
485 |
486 | for (var i = queries.length - 1; i >= 0; i--){
487 | queries[i][0].each(function() {
488 | items.push(this);
489 | });
490 | };
491 |
492 | return $(items);
493 |
494 | },
495 |
496 | _removeCurrentsFromItems: function() {
497 |
498 | var list = this.currentItem.find(":data(sortable-item)");
499 |
500 | for (var i=0; i < this.items.length; i++) {
501 |
502 | for (var j=0; j < list.length; j++) {
503 | if(list[j] == this.items[i].item[0])
504 | this.items.splice(i,1);
505 | };
506 |
507 | };
508 |
509 | },
510 |
511 | _refreshItems: function(event) {
512 |
513 | this.items = [];
514 | this.containers = [this];
515 | var items = this.items;
516 | var self = this;
517 | var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
518 |
519 | if(this.options.connectWith) {
520 | for (var i = this.options.connectWith.length - 1; i >= 0; i--){
521 | var cur = $(this.options.connectWith[i]);
522 | for (var j = cur.length - 1; j >= 0; j--){
523 | var inst = $.data(cur[j], 'sortable');
524 | if(inst && inst != this && !inst.options.disabled) {
525 | queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
526 | this.containers.push(inst);
527 | }
528 | };
529 | };
530 | }
531 |
532 | for (var i = queries.length - 1; i >= 0; i--) {
533 | var targetData = queries[i][1];
534 | var _queries = queries[i][0];
535 |
536 | for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
537 | var item = $(_queries[j]);
538 |
539 | item.data('sortable-item', targetData); // Data for target checking (mouse manager)
540 |
541 | items.push({
542 | item: item,
543 | instance: targetData,
544 | width: 0, height: 0,
545 | left: 0, top: 0
546 | });
547 | };
548 | };
549 |
550 | },
551 |
552 | refreshPositions: function(fast) {
553 |
554 | //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
555 | if(this.offsetParent && this.helper) {
556 | this.offset.parent = this._getParentOffset();
557 | }
558 |
559 | for (var i = this.items.length - 1; i >= 0; i--){
560 | var item = this.items[i];
561 |
562 | //We ignore calculating positions of all connected containers when we're not over them
563 | if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
564 | continue;
565 |
566 | var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
567 |
568 | if (!fast) {
569 | if (this.options.accurateIntersection) {
570 | item.width = t.outerWidth();
571 | item.height = t.outerHeight();
572 | }
573 | else {
574 | item.width = t[0].offsetWidth;
575 | item.height = t[0].offsetHeight;
576 | }
577 | }
578 |
579 | var p = t.offset();
580 | item.left = p.left;
581 | item.top = p.top;
582 | };
583 |
584 | if(this.options.custom && this.options.custom.refreshContainers) {
585 | this.options.custom.refreshContainers.call(this);
586 | } else {
587 | for (var i = this.containers.length - 1; i >= 0; i--){
588 | var p = this.containers[i].element.offset();
589 | this.containers[i].containerCache.left = p.left;
590 | this.containers[i].containerCache.top = p.top;
591 | this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
592 | this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
593 | };
594 | }
595 |
596 | },
597 |
598 | _createPlaceholder: function(that) {
599 |
600 | var self = that || this, o = self.options;
601 |
602 | if(!o.placeholder || o.placeholder.constructor == String) {
603 | var className = o.placeholder;
604 | o.placeholder = {
605 | element: function() {
606 |
607 | var el = $(document.createElement(self.currentItem[0].nodeName))
608 | .addClass(className || self.currentItem[0].className+" "+self.options.cssNamespace+"-sortable-placeholder")
609 | .removeClass(self.options.cssNamespace+'-sortable-helper')[0];
610 |
611 | if(!className)
612 | el.style.visibility = "hidden";
613 |
614 | return el;
615 | },
616 | update: function(container, p) {
617 |
618 | // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
619 | // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
620 | if(className && !o.forcePlaceholderSize) return;
621 |
622 | //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
623 | if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); };
624 | if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); };
625 | }
626 | };
627 | }
628 |
629 | //Create the placeholder
630 | self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem));
631 |
632 | //Append it after the actual current item
633 | self.currentItem.after(self.placeholder);
634 |
635 | //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
636 | o.placeholder.update(self, self.placeholder);
637 |
638 | },
639 |
640 | _contactContainers: function(event) {
641 | for (var i = this.containers.length - 1; i >= 0; i--){
642 |
643 | if(this._intersectsWith(this.containers[i].containerCache)) {
644 | if(!this.containers[i].containerCache.over) {
645 |
646 | if(this.currentContainer != this.containers[i]) {
647 |
648 | //When entering a new container, we will find the item with the least distance and append our item near it
649 | var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[i].floating ? 'left' : 'top'];
650 | for (var j = this.items.length - 1; j >= 0; j--) {
651 | if(!$.ui.contains(this.containers[i].element[0], this.items[j].item[0])) continue;
652 | var cur = this.items[j][this.containers[i].floating ? 'left' : 'top'];
653 | if(Math.abs(cur - base) < dist) {
654 | dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
655 | }
656 | }
657 |
658 | if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
659 | continue;
660 |
661 | this.currentContainer = this.containers[i];
662 | itemWithLeastDistance ? this.options.sortIndicator.call(this, event, itemWithLeastDistance, null, true) : this.options.sortIndicator.call(this, event, null, this.containers[i].element, true);
663 | this._trigger("change", event, this._uiHash());
664 | this.containers[i]._trigger("change", event, this._uiHash(this));
665 |
666 | //Update the placeholder
667 | this.options.placeholder.update(this.currentContainer, this.placeholder);
668 |
669 | }
670 |
671 | this.containers[i]._trigger("over", event, this._uiHash(this));
672 | this.containers[i].containerCache.over = 1;
673 | }
674 | } else {
675 | if(this.containers[i].containerCache.over) {
676 | this.containers[i]._trigger("out", event, this._uiHash(this));
677 | this.containers[i].containerCache.over = 0;
678 | }
679 | }
680 |
681 | };
682 | },
683 |
684 | _createHelper: function(event) {
685 |
686 | var o = this.options;
687 | var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);
688 |
689 | if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
690 | $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
691 |
692 | if(helper[0] == this.currentItem[0])
693 | this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
694 |
695 | if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
696 | if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());
697 |
698 | return helper;
699 |
700 | },
701 |
702 | _adjustOffsetFromHelper: function(obj) {
703 | if(obj.left != undefined) this.offset.click.left = obj.left + this.margins.left;
704 | if(obj.right != undefined) this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
705 | if(obj.top != undefined) this.offset.click.top = obj.top + this.margins.top;
706 | if(obj.bottom != undefined) this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
707 | },
708 |
709 | _getParentOffset: function() {
710 |
711 |
712 | //Get the offsetParent and cache its position
713 | this.offsetParent = this.helper.offsetParent();
714 | var po = this.offsetParent.offset();
715 |
716 | // This is a special case where we need to modify a offset calculated on start, since the following happened:
717 | // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
718 | // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
719 | // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
720 | if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
721 | po.left += this.scrollParent.scrollLeft();
722 | po.top += this.scrollParent.scrollTop();
723 | }
724 |
725 | if((this.offsetParent[0] == document.body && $.browser.mozilla) //Ugly FF3 fix
726 | || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
727 | po = { top: 0, left: 0 };
728 |
729 | return {
730 | top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
731 | left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
732 | };
733 |
734 | },
735 |
736 | _getRelativeOffset: function() {
737 |
738 | if(this.cssPosition == "relative") {
739 | var p = this.currentItem.position();
740 | return {
741 | top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
742 | left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
743 | };
744 | } else {
745 | return { top: 0, left: 0 };
746 | }
747 |
748 | },
749 |
750 | _cacheMargins: function() {
751 | this.margins = {
752 | left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
753 | top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
754 | };
755 | },
756 |
757 | _cacheHelperProportions: function() {
758 | this.helperProportions = {
759 | width: this.helper.outerWidth(),
760 | height: this.helper.outerHeight()
761 | };
762 | },
763 |
764 | _setContainment: function() {
765 |
766 | var o = this.options;
767 | if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
768 | if(o.containment == 'document' || o.containment == 'window') this.containment = [
769 | 0 - this.offset.relative.left - this.offset.parent.left,
770 | 0 - this.offset.relative.top - this.offset.parent.top,
771 | $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
772 | ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
773 | ];
774 |
775 | if(!(/^(document|window|parent)$/).test(o.containment)) {
776 | var ce = $(o.containment)[0];
777 | var co = $(o.containment).offset();
778 | var over = ($(ce).css("overflow") != 'hidden');
779 |
780 | this.containment = [
781 | co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
782 | co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
783 | co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
784 | co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
785 | ];
786 | }
787 |
788 | },
789 |
790 | _convertPositionTo: function(d, pos) {
791 |
792 | if(!pos) pos = this.position;
793 | var mod = d == "absolute" ? 1 : -1;
794 | var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
795 |
796 | return {
797 | top: (
798 | pos.top // The absolute mouse position
799 | + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
800 | + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
801 | - ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod
802 | ),
803 | left: (
804 | pos.left // The absolute mouse position
805 | + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
806 | + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
807 | - ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod
808 | )
809 | };
810 |
811 | },
812 |
813 | _generatePosition: function(event) {
814 |
815 | var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
816 |
817 | // This is another very weird special case that only happens for relative elements:
818 | // 1. If the css position is relative
819 | // 2. and the scroll parent is the document or similar to the offset parent
820 | // we have to refresh the relative offset during the scroll so there are no jumps
821 | if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
822 | this.offset.relative = this._getRelativeOffset();
823 | }
824 |
825 | var pageX = event.pageX;
826 | var pageY = event.pageY;
827 |
828 | /*
829 | * - Position constraining -
830 | * Constrain the position to a mix of grid, containment.
831 | */
832 |
833 | if(this.originalPosition) { //If we are not dragging yet, we won't check for options
834 |
835 | if(this.containment) {
836 | if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
837 | if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
838 | if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
839 | if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
840 | }
841 |
842 | if(o.grid) {
843 | var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
844 | pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
845 |
846 | var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
847 | pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
848 | }
849 |
850 | }
851 |
852 | return {
853 | top: (
854 | pageY // The absolute mouse position
855 | - this.offset.click.top // Click offset (relative to the element)
856 | - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
857 | - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
858 | + ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )
859 | ),
860 | left: (
861 | pageX // The absolute mouse position
862 | - this.offset.click.left // Click offset (relative to the element)
863 | - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
864 | - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
865 | + ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )
866 | )
867 | };
868 |
869 | },
870 |
871 | _rearrange: function(event, i, a, hardRefresh) {
872 |
873 | a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));
874 |
875 | //Various things done here to improve the performance:
876 | // 1. we create a setTimeout, that calls refreshPositions
877 | // 2. on the instance, we have a counter variable, that get's higher after every append
878 | // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
879 | // 4. this lets only the last addition to the timeout stack through
880 | this.counter = this.counter ? ++this.counter : 1;
881 | var self = this, counter = this.counter;
882 |
883 | window.setTimeout(function() {
884 | if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
885 | },0);
886 |
887 | },
888 |
889 | _clear: function(event, noPropagation) {
890 |
891 | this.reverting = false;
892 | // We delay all events that have to be triggered to after the point where the placeholder has been removed and
893 | // everything else normalized again
894 | var delayedTriggers = [], self = this;
895 |
896 | //We first have to update the dom position of the actual currentItem
897 | if(!this._noFinalSort) this.placeholder.before(this.currentItem);
898 | this._noFinalSort = null;
899 |
900 | if(this.helper[0] == this.currentItem[0]) {
901 | for(var i in this._storedCSS) {
902 | if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
903 | }
904 | this.currentItem.css(this._storedCSS).removeClass(this.options.cssNamespace+"-sortable-helper");
905 | } else {
906 | this.currentItem.show();
907 | }
908 |
909 | if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
910 | if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not("."+this.options.cssNamespace+"-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
911 | if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
912 | if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
913 | for (var i = this.containers.length - 1; i >= 0; i--){
914 | if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) {
915 | delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
916 | delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
917 | }
918 | };
919 | };
920 |
921 | //Post events to containers
922 | for (var i = this.containers.length - 1; i >= 0; i--){
923 | if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
924 | if(this.containers[i].containerCache.over) {
925 | delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
926 | this.containers[i].containerCache.over = 0;
927 | }
928 | }
929 |
930 | //Do what was originally in plugins
931 | if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
932 | if(this._storedOpacity) this.helper.css("opacity", this._storedCursor); //Reset cursor
933 | if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index
934 |
935 | this.dragging = false;
936 | if(this.cancelHelperRemoval) {
937 | if(!noPropagation) {
938 | this._trigger("beforeStop", event, this._uiHash());
939 | for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
940 | this._trigger("stop", event, this._uiHash());
941 | }
942 | return false;
943 | }
944 |
945 | if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());
946 |
947 | //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
948 | this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
949 |
950 | if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;
951 |
952 | if(!noPropagation) {
953 | for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
954 | this._trigger("stop", event, this._uiHash());
955 | }
956 |
957 | this.fromOutside = false;
958 | return true;
959 |
960 | },
961 |
962 | _trigger: function() {
963 | if ($.widget.prototype._trigger.apply(this, arguments) === false) {
964 | this.cancel();
965 | }
966 | },
967 |
968 | _uiHash: function(inst) {
969 | var self = inst || this;
970 | return {
971 | helper: self.helper,
972 | placeholder: self.placeholder || $([]),
973 | position: self.position,
974 | absolutePosition: self.positionAbs, //deprecated
975 | offset: self.positionAbs,
976 | item: self.currentItem,
977 | sender: inst ? inst.element : null
978 | };
979 | }
980 |
981 | }));
982 |
983 | $.extend($.ui.sortable, {
984 | getter: "serialize toArray",
985 | version: "1.6rc6",
986 | defaults: {
987 | accurateIntersection: true,
988 | appendTo: "parent",
989 | cancel: ":input,option",
990 | connectWith: false,
991 | cssNamespace: 'ui',
992 | delay: 0,
993 | distance: 1,
994 | dropOnEmpty: true,
995 | forcePlaceholderSize: false,
996 | forceHelperSize: false,
997 | handle: false,
998 | helper: "original",
999 | items: '> *',
1000 | placeholder: false,
1001 | scope: "default",
1002 | scroll: true,
1003 | scrollSensitivity: 20,
1004 | scrollSpeed: 20,
1005 | sortIndicator: $.ui.sortable.prototype._rearrange,
1006 | tolerance: "intersect",
1007 | zIndex: 1000
1008 | }
1009 | });
1010 |
1011 | })(jQuery);
1012 |
--------------------------------------------------------------------------------
/conference/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Deepwalker/fs2web/a8807ae30d02d4a11e47fdbf9a9948a1bea8c1d4/conference/__init__.py
--------------------------------------------------------------------------------
/conference/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from models import *
3 |
4 | def start_conf(modeladmin, request, queryset):
5 | for obj in queryset:
6 | obj.start()
7 | start_conf.short_description = "Start conference"
8 |
9 | class ConferenceAdmin(admin.ModelAdmin):
10 | actions=[start_conf]
11 |
12 |
13 | admin.site.register(Conference,ConferenceAdmin)
14 | admin.site.register(Phone)
15 |
16 |
--------------------------------------------------------------------------------
/conference/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models as m
2 | from django.conf import settings
3 | from django.utils.translation import ugettext_lazy as _
4 | from django import forms
5 | from fsapi import *
6 |
7 | # Create your models here.
8 |
9 | class Conference(m.Model):
10 | name = m.CharField(_(u"Name"),max_length=254,blank=False)
11 | number = m.CharField(_(u"Number"),max_length=254,blank=False)
12 | pin = m.IntegerField(_(u"Password"),max_length=254,blank=True,null=True)
13 | participants = m.ManyToManyField("Phone",verbose_name=_(u"Phone"),through='Participant')
14 | is_active = m.BooleanField(_(u"Is active?"),default=False,editable=False)
15 | def start(self):
16 | #participants = list(self.participants.filter(auto_call=True))
17 | # TODO: auto_call filter
18 | participants = self.participant_set.all()
19 | if participants:
20 | for p in participants:
21 | call_from_conference(self.number,p.phone.number,vars='participant=%s'%p.id)
22 | def participants_form(self):
23 | return AddParticipant(instance=self)
24 | def __unicode__(self):
25 | return self.name
26 | class Meta:
27 | verbose_name = _(u"Conference")
28 | verbose_name_plural = _(u"Conferences")
29 |
30 | class Phone(m.Model):
31 | name = m.CharField(_(u"Name"),max_length=254)
32 | number = m.IntegerField(_(u"Number"),max_length=254)
33 | auto_call = m.BooleanField(_(u"Auto call"),default=True)
34 | def caller_id_name(self):
35 | return self.name
36 | def caller_id_number(self):
37 | return self.number
38 | def member(self):
39 | return self
40 | def __unicode__(self):
41 | return "%s (%s)%s"%(self.name,self.number,"" if self.auto_call else "!")
42 | class Meta:
43 | verbose_name = _(u"Phone")
44 | verbose_name_plural = _(u"Phones")
45 |
46 | class Participant(m.Model):
47 | conference = m.ForeignKey(Conference)
48 | phone = m.ForeignKey(Phone)
49 | active = m.BooleanField(_(u"Active"),default=False,editable=False)
50 | mute = m.BooleanField(_(u"Mute"),default=False,editable=False)
51 | talk = m.BooleanField(_(u"Talk"),default=False,editable=False)
52 | deaf = m.BooleanField(_(u"Deaf"),default=False,editable=False)
53 |
54 | # Forms
55 | class AddParticipant(forms.ModelForm):
56 | class Meta:
57 | model = Conference
58 | fields = ['participants']
59 | def save(self):
60 | for p in self.cleaned_data['participants']:
61 | Participant.objects.get_or_create(conference=self.instance,phone=p)
62 | Participant.objects.filter(conference=self.instance).\
63 | exclude(phone__in=self.cleaned_data['participants']).delete()
64 |
65 |
66 | class InviteParticipantForm(forms.Form):
67 | conference = forms.CharField(widget=forms.widgets.HiddenInput())
68 | phone = forms.CharField()
69 |
70 | class AddConference(forms.ModelForm):
71 | class Meta:
72 | model = Conference
73 | fields = ['name','number']
74 |
--------------------------------------------------------------------------------
/conference/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | urlpatterns = patterns('',
4 | (r'^$','conference.views.list'),
5 | (r'^(?P[-.0-9a-zA-Z]+)/(?Pkick|mute|unmute|start)/(?P\d+)/$','conference.views.list'),
6 | (r'^add/$','conference.views.add'),
7 | (r'^add/participants/(?P\d+)/$','conference.views.add_partcipants'),
8 | (r'^delete/(?P\d+)/$','conference.views.del_conf'),
9 | )
10 |
--------------------------------------------------------------------------------
/conference/views.py:
--------------------------------------------------------------------------------
1 | from lxml import objectify as O
2 | from django.shortcuts import render_to_response, get_object_or_404
3 | from django.http import HttpResponseRedirect
4 | from django.conf import settings
5 | from django.views.generic import list_detail
6 | from models import *
7 | from fsapi import *
8 |
9 | def list(request,do="",id="",cnf="",param=""):
10 | # Data from FreeSWITCH
11 | r = O.fromstring(fsapi("conference","xml_list"))
12 |
13 | # Commands to FreeSWITCH
14 | if do=="start":
15 | conference = Conference.objects.get(id=int(cnf))
16 | conference.start()
17 | return HttpResponseRedirect('/confs/')
18 | if do and id:
19 | fsapi("conference","%s %s %s"%(cnf,do,id))
20 | return HttpResponseRedirect('/confs/')
21 |
22 | # Invite participant
23 | if request.GET:
24 | form = InviteParticipantForm(request.GET)
25 | if form.is_valid():
26 | conf = form.cleaned_data.get('conference')
27 | phone = form.cleaned_data.get('phone')
28 | call_from_conference(conf,phone)
29 | return HttpResponseRedirect('/confs/')
30 |
31 | # Parse data for display
32 | conferences = []
33 | active_confs=[]
34 | if r.countchildren() > 0:
35 | for conf in r.conference:
36 | conf_name =conf.get('name')
37 | conference = Conference.objects.filter(number=conf_name)
38 | if conference:
39 | members = dict([(str(m.number),m) for m in conference[0].participants.all()])
40 | conference[0].is_active = True
41 | conference[0].save()
42 | active_confs.append(conference[0].id)
43 | else:
44 | members = {}
45 | fs_members = [m for m in conf.members.member]
46 | for fm in fs_members:
47 | if str(fm.caller_id_name) in members:
48 | fm.member = members[str(fm.caller_id_name)]
49 | del members[str(fm.caller_id_name)]
50 | else:
51 | fm.member = None
52 | fs_members.extend(members.values())
53 | conf_res = [conf_name, fs_members,InviteParticipantForm({'conference':conf.get('name')}),
54 | conference[0] if conference else None]
55 | conferences.append(conf_res)
56 | Conference.objects.exclude(id__in=active_confs).update(is_active=False)
57 | return list_detail.object_list(request,queryset=Conference.objects.filter(is_active=False),
58 | template_name='confrences.html',extra_context={'confs':conferences,'addconf':AddConference()})
59 | #return render_to_response('confrences.html',{'confs':conferences})
60 |
61 | def add(request):
62 | # Add conference
63 | if request.method == "POST":
64 | new_conf = AddConference(request.POST)
65 | if new_conf.is_valid():
66 | new_conf.save()
67 | return HttpResponseRedirect('/confs/')
68 | return HttpResponseRedirect('/confs/')
69 |
70 | def add_partcipants(request,object_id):
71 | # Update participants
72 | conf=get_object_or_404(Conference,pk=object_id)
73 | if request.method == "POST":
74 | new_conf = AddParticipant(request.POST,instance=conf)
75 | if new_conf.is_valid():
76 | new_conf.save()
77 | return HttpResponseRedirect('/confs/')
78 | return HttpResponseRedirect('/confs/')
79 |
80 | def del_conf(request,object_id):
81 | # Update participants
82 | conf=get_object_or_404(Conference,pk=object_id)
83 | conf.delete()
84 | return HttpResponseRedirect('/confs/')
85 |
--------------------------------------------------------------------------------
/debug.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | import syslog
5 |
6 | def debug(msg):
7 | print msg
8 | syslog.syslog(msg)
9 |
--------------------------------------------------------------------------------
/dialplan/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Deepwalker/fs2web/a8807ae30d02d4a11e47fdbf9a9948a1bea8c1d4/dialplan/__init__.py
--------------------------------------------------------------------------------
/dialplan/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from models import *
3 |
4 | #class VarInline(admin.TabularInline):
5 | # model = Agent
6 | # extra = 3
7 | #
8 | #class FSGAdmin(admin.ModelAdmin):
9 | # inlines = [VarInline]
10 | #
11 | #admin.site.register(HuntGroup,FSGAdmin)
12 |
13 | class ExtInline(admin.TabularInline):
14 | model = Extension
15 | extra = 5
16 | template = "condition_inline.html"
17 | class CondInline(admin.TabularInline):
18 | model = Condition
19 | extra = 3
20 | template = "condition_inline.html"
21 | class ActInline(admin.TabularInline):
22 | model = Action
23 | extra = 10
24 |
25 | class Cont_Admin(admin.ModelAdmin):
26 | inlines = [ExtInline]
27 | class Ext_Admin(admin.ModelAdmin):
28 | inlines = [CondInline]
29 | class Cond_Admin(admin.ModelAdmin):
30 | inlines = [ActInline]
31 |
32 | admin.site.register(Extension, Ext_Admin)
33 | admin.site.register(Condition, Cond_Admin)
34 | admin.site.register(Context,Cont_Admin)
35 |
36 | admin.site.register(DPApp)
37 |
38 |
--------------------------------------------------------------------------------
/dialplan/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models as m
2 | from django.utils.translation import ugettext_lazy as _
3 | #from users.models import PhoneNumber
4 |
5 | # Create your models here.
6 | class Context(m.Model):
7 | name = m.CharField(_(u"Name"),max_length=255)
8 | def __unicode__(self):
9 | return self.name
10 | class Meta:
11 | verbose_name = _(u"Context")
12 | verbose_name_plural = _(u"Contexts")
13 |
14 | class Extension(m.Model):
15 | name = m.CharField(_(u"Name"),max_length=255)
16 | continue_on = m.BooleanField(_(u"Continue on"))
17 | context = m.ForeignKey(Context)
18 | pref = m.IntegerField(_(u"Preference"))
19 | def __unicode__(self):
20 | return self.name
21 | class Meta:
22 | verbose_name = _(u"Extension")
23 | verbose_name_plural = _(u"Extensions")
24 |
25 | BREAK = (
26 | ("on-false",_(u"on-false")),
27 | ("on-true",_(u"on-true")),
28 | ("always",_(u"always")),
29 | ("never",_(u"never")),
30 | )
31 |
32 | class Condition(m.Model):
33 | field = m.CharField(_(u"Field"),max_length=255)
34 | expression = m.CharField(_(u"Expression"),max_length=255)
35 | break_on = m.CharField(_(u"Break on"),max_length=10,choices=BREAK,default="on-false")
36 | extension = m.ForeignKey(Extension)
37 | pref = m.IntegerField(_(u"Preference"),)
38 | def __unicode__(self):
39 | return "%s === %s"%(self.field,self.expression)
40 | class Meta:
41 | ordering = ['pref']
42 | verbose_name = _(u"Condition")
43 | verbose_name_plural = _(u"Conditions")
44 |
45 | class DPApp(m.Model):
46 | name = m.CharField(_(u"Name"),max_length=255)
47 | def __unicode__(self):
48 | return self.name
49 | class Meta:
50 | verbose_name = _(u"Dialplan application")
51 | verbose_name_plural = _(u"Dialplan applications")
52 |
53 | def get_dpapp(app):
54 | f = DPApp.objects.filter(name=app)
55 | if f:
56 | return f[0]
57 | else:
58 | new = DPApp(name=app)
59 | new.save()
60 | return new
61 |
62 | class Action(m.Model):
63 | app = m.ForeignKey(DPApp)
64 | condition = m.ForeignKey(Condition)
65 | params = m.CharField(_(u"Name"),max_length=255)
66 | anti = m.BooleanField(_(u"Anti action"))
67 | pref = m.IntegerField(_(u"Preference"))
68 | def __unicode__(self):
69 | return "%s(%s)"%(self.app.name,self.params)
70 | class Meta:
71 | ordering = ['pref']
72 | verbose_name = _(u"Action")
73 | verbose_name_plural = _(u"Actions")
74 |
75 |
--------------------------------------------------------------------------------
/dialplan/templates/condition_inline.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |