4 * Copyright, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://www.tinymce.com/license
8 * Contributing: http://www.tinymce.com/contributing
12 * Selector engine, enables you to select controls by using CSS like expressions.
13 * We currently only support basic CSS expressions to reduce the size of the core
14 * and the ones we support should be enough for most cases.
17 * Supported expressions:
22 * element[attr*=value]
23 * element[attr~=value]
24 * element[attr!=value]
25 * element[attr^=value]
26 * element[attr$=value]
28 * element:not(<expression>)
36 * @class tinymce.ui.Selector
38 define("tinymce/ui/Selector", [
41 ], function(Class, Tools) {
45 * Produces an array with a unique set of objects. It will not compare the values
46 * but the references of the objects.
50 * @param {Array} array Array to make into an array with unique items.
51 * @return {Array} Array with unique items.
53 function unique(array) {
54 var uniqueItems = [], i = array.length, item;
59 if (!item.__checked) {
60 uniqueItems.push(item);
65 i = uniqueItems.length;
67 delete uniqueItems[i].__checked;
73 var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;
75 /*jshint maxlen:255 */
76 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
77 whiteSpace = /^\s*|\s*$/g,
80 var Selector = Class.extend({
82 * Constructs a new Selector instance.
86 * @param {String} selector CSS like selector expression.
88 init: function(selector) {
89 var match = this.match;
91 function compileNameFilter(name) {
93 name = name.toLowerCase();
95 return function(item) {
96 return name === '*' || item.type === name;
101 function compileIdFilter(id) {
103 return function(item) {
104 return item._name === id;
109 function compileClassesFilter(classes) {
111 classes = classes.split('.');
113 return function(item) {
114 var i = classes.length;
117 if (!item.hasClass(classes[i])) {
127 function compileAttrFilter(name, cmp, check) {
129 return function(item) {
130 var value = item[name] ? item[name]() : '';
132 return !cmp ? !!check :
133 cmp === "=" ? value === check :
134 cmp === "*=" ? value.indexOf(check) >= 0 :
135 cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 :
136 cmp === "!=" ? value != check :
137 cmp === "^=" ? value.indexOf(check) === 0 :
138 cmp === "$=" ? value.substr(value.length - check.length) === check :
144 function compilePsuedoFilter(name) {
148 name = /(?:not\((.+)\))|(.+)/i.exec(name);
153 return function(item, index, length) {
154 return name === 'first' ? index === 0 :
155 name === 'last' ? index === length - 1 :
156 name === 'even' ? index % 2 === 0 :
157 name === 'odd' ? index % 2 === 1 :
158 item[name] ? item[name]() :
162 // Compile not expression
163 notSelectors = parseChunks(name[1], []);
165 return function(item) {
166 return !match(item, notSelectors);
172 function compile(selector, filters, direct) {
175 function add(filter) {
177 filters.push(filter);
181 // Parse expression into parts
182 parts = expression.exec(selector.replace(whiteSpace, ''));
184 add(compileNameFilter(parts[1]));
185 add(compileIdFilter(parts[2]));
186 add(compileClassesFilter(parts[3]));
187 add(compileAttrFilter(parts[4], parts[5], parts[6]));
188 add(compilePsuedoFilter(parts[7]));
190 // Mark the filter with psuedo for performance
191 filters.psuedo = !!parts[7];
192 filters.direct = direct;
197 // Parser logic based on Sizzle by John Resig
198 function parseChunks(selector, selectors) {
199 var parts = [], extra, matches, i;
203 matches = chunker.exec(selector);
206 selector = matches[3];
207 parts.push(matches[1]);
217 parseChunks(extra, selectors);
221 for (i = 0; i < parts.length; i++) {
222 if (parts[i] != '>') {
223 selector.push(compile(parts[i], [], parts[i - 1] === '>'));
227 selectors.push(selector);
232 this._selectors = parseChunks(selector, []);
236 * Returns true/false if the selector matches the specified control.
239 * @param {tinymce.ui.Control} control Control to match agains the selector.
240 * @param {Array} selectors Optional array of selectors, mostly used internally.
241 * @return {Boolean} true/false state if the control matches or not.
243 match: function(control, selectors) {
244 var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item;
246 selectors = selectors || this._selectors;
247 for (i = 0, l = selectors.length; i < l; i++) {
248 selector = selectors[i];
249 sl = selector.length;
253 for (si = sl - 1; si >= 0; si--) {
254 filters = selector[si];
257 // Find the index and length since a psuedo filter like :first needs it
258 if (filters.psuedo) {
259 siblings = item.parent().items();
260 index = Tools.inArray(item, siblings);
261 length = siblings.length;
264 for (fi = 0, fl = filters.length; fi < fl; fi++) {
265 if (!filters[fi](item, index, length)) {
275 // If it didn't match the right most expression then
276 // break since it's no point looking at the parents
282 item = item.parent();
286 // If we found all selectors then return true otherwise continue looking
296 * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container.
299 * @param {tinymce.ui.Control} container Container to look for items in.
300 * @return {tinymce.ui.Collection} Collection with matched elements.
302 find: function(container) {
303 var matches = [], i, l, selectors = this._selectors;
305 function collect(items, selector, index) {
306 var i, l, fi, fl, item, filters = selector[index];
308 for (i = 0, l = items.length; i < l; i++) {
311 // Run each filter agains the item
312 for (fi = 0, fl = filters.length; fi < fl; fi++) {
313 if (!filters[fi](item, i, l)) {
319 // All filters matched the item
321 // Matched item is on the last expression like: panel toolbar [button]
322 if (index == selector.length - 1) {
325 // Collect next expression type
327 collect(item.items(), selector, index + 1);
330 } else if (filters.direct) {
334 // Collect child items
336 collect(item.items(), selector, index);
341 if (container.items) {
342 for (i = 0, l = selectors.length; i < l; i++) {
343 collect(container.items(), selectors[i], 0);
346 // Unique the matches if needed
348 matches = unique(matches);
352 // Fix for circular reference
355 Collection = Selector.Collection;
358 return new Collection(matches);