maccman-bowline 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. data/History.txt +4 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest.txt +58 -0
  4. data/README.txt +113 -0
  5. data/Rakefile +24 -0
  6. data/assets/jquery.bowline.js +96 -0
  7. data/assets/jquery.chain.js +2348 -0
  8. data/assets/jquery.js +3549 -0
  9. data/bin/bowline-gen +6 -0
  10. data/bowline.gemspec +42 -0
  11. data/examples/account_binder.rb +29 -0
  12. data/examples/example.js +24 -0
  13. data/examples/twitter.html +43 -0
  14. data/examples/twitter_binder.rb +40 -0
  15. data/examples/twitter_login.html +29 -0
  16. data/examples/users_binder.rb +39 -0
  17. data/lib/bowline.rb +42 -0
  18. data/lib/bowline/binders.rb +177 -0
  19. data/lib/bowline/binders/collection.rb +27 -0
  20. data/lib/bowline/binders/singleton.rb +25 -0
  21. data/lib/bowline/commands/console.rb +27 -0
  22. data/lib/bowline/commands/generate.rb +1 -0
  23. data/lib/bowline/commands/run.rb +13 -0
  24. data/lib/bowline/ext/array.rb +5 -0
  25. data/lib/bowline/ext/class.rb +51 -0
  26. data/lib/bowline/ext/object.rb +12 -0
  27. data/lib/bowline/ext/string.rb +9 -0
  28. data/lib/bowline/gem_dependency.rb +42 -0
  29. data/lib/bowline/generators.rb +59 -0
  30. data/lib/bowline/generators/application.rb +49 -0
  31. data/lib/bowline/generators/binder.rb +25 -0
  32. data/lib/bowline/generators/migration.rb +51 -0
  33. data/lib/bowline/generators/model.rb +20 -0
  34. data/lib/bowline/initializer.rb +596 -0
  35. data/lib/bowline/jquery.rb +31 -0
  36. data/lib/bowline/observer.rb +43 -0
  37. data/lib/bowline/tasks/app.rake +70 -0
  38. data/lib/bowline/tasks/bowline.rb +8 -0
  39. data/lib/bowline/tasks/database.rake +167 -0
  40. data/lib/bowline/tasks/log.rake +9 -0
  41. data/lib/bowline/tasks/misk.rake +3 -0
  42. data/templates/Rakefile +7 -0
  43. data/templates/binder.rb +9 -0
  44. data/templates/config/application.yml +1 -0
  45. data/templates/config/boot.rb +21 -0
  46. data/templates/config/database.yml +4 -0
  47. data/templates/config/environment.rb +12 -0
  48. data/templates/config/manifest +18 -0
  49. data/templates/config/tiapp.xml +24 -0
  50. data/templates/gitignore +15 -0
  51. data/templates/migration.rb +7 -0
  52. data/templates/model.rb +4 -0
  53. data/templates/public/index.html.erb +23 -0
  54. data/templates/public/javascripts/application.js +0 -0
  55. data/templates/public/stylesheets/application.css +0 -0
  56. data/templates/script/console +3 -0
  57. data/templates/script/init +11 -0
  58. data/templates/script/run +3 -0
  59. metadata +143 -0
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-04-23
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Made by Many Limited and Alexander MacCaw
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,58 @@
1
+ History.txt
2
+ MIT-LICENSE
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ assets/jquery.bowline.js
7
+ assets/jquery.chain.js
8
+ assets/jquery.js
9
+ bin/bowline-gen
10
+ bowline.gemspec
11
+ examples/account_binder.rb
12
+ examples/example.js
13
+ examples/twitter.html
14
+ examples/twitter_binder.rb
15
+ examples/twitter_login.html
16
+ examples/users_binder.rb
17
+ lib/bowline.rb
18
+ lib/bowline/binders.rb
19
+ lib/bowline/binders/collection.rb
20
+ lib/bowline/binders/singleton.rb
21
+ lib/bowline/commands/console.rb
22
+ lib/bowline/commands/generate.rb
23
+ lib/bowline/commands/run.rb
24
+ lib/bowline/ext/array.rb
25
+ lib/bowline/ext/class.rb
26
+ lib/bowline/ext/object.rb
27
+ lib/bowline/ext/string.rb
28
+ lib/bowline/gem_dependency.rb
29
+ lib/bowline/generators.rb
30
+ lib/bowline/generators/application.rb
31
+ lib/bowline/generators/binder.rb
32
+ lib/bowline/generators/migration.rb
33
+ lib/bowline/generators/model.rb
34
+ lib/bowline/initializer.rb
35
+ lib/bowline/jquery.rb
36
+ lib/bowline/observer.rb
37
+ lib/bowline/tasks/app.rake
38
+ lib/bowline/tasks/bowline.rb
39
+ lib/bowline/tasks/database.rake
40
+ lib/bowline/tasks/log.rake
41
+ lib/bowline/tasks/misk.rake
42
+ templates/Rakefile
43
+ templates/binder.rb
44
+ templates/config/application.yml
45
+ templates/config/boot.rb
46
+ templates/config/database.yml
47
+ templates/config/environment.rb
48
+ templates/config/manifest
49
+ templates/config/tiapp.xml
50
+ templates/gitignore
51
+ templates/migration.rb
52
+ templates/model.rb
53
+ templates/public/index.html.erb
54
+ templates/public/javascripts/application.js
55
+ templates/public/stylesheets/application.css
56
+ templates/script/console
57
+ templates/script/init
58
+ templates/script/run
data/README.txt ADDED
@@ -0,0 +1,113 @@
1
+ = Bowline
2
+
3
+ http://github.com/maccman/bowline
4
+
5
+ = DESCRIPTION
6
+
7
+ My take on Ruby desktop GUIs
8
+
9
+ = FEATURES
10
+
11
+ * Cross platform
12
+ * MVC
13
+ * Uses Webkit
14
+ * View in HTML/JavaScript
15
+ * Binding between HTML & Ruby
16
+
17
+ = CONTACT
18
+
19
+ info@eribium.org
20
+ http://eribium.org
21
+ http://twitter.com/maccman
22
+
23
+ = Usage
24
+
25
+ bowline-gen app bowline_twitter
26
+ cd bowline_twitter
27
+ bowline-gen binder twitter
28
+
29
+
30
+ = EXAMPLES
31
+
32
+ Have a look at the examples for an example twitter client.
33
+
34
+ Usage for a collection (of users):
35
+
36
+ module Binders
37
+ class Users < Bowline::Collection
38
+ # These are class methods
39
+ # i.e. methods that appear on
40
+ # users, rather an user
41
+ class << self
42
+ def index
43
+ # self.items is a magic variable -
44
+ # it'll update the html binders
45
+ self.items = User.all
46
+ end
47
+
48
+ def admins
49
+ # This just replaces all the listed
50
+ # users with just admins
51
+ self.items = User.admins.all
52
+ end
53
+ end
54
+
55
+ # Singleton methods, get added
56
+ # to individual users.
57
+ #
58
+ # self.element is the jQuery element
59
+ # for that user, so calling highlight
60
+ # on it is equivalent to:
61
+ # $(user).highlight()
62
+ #
63
+ # self.item is the user object, in this case
64
+ # an ActiveRecord instance
65
+ #
66
+ # self.page gives you access to the dom, e.g:
67
+ # self.page.alert('hello world')
68
+
69
+ def destroy
70
+ self.item.destroy
71
+ self.element.remove
72
+ end
73
+ end
74
+ end
75
+
76
+ <html>
77
+ <head>
78
+ <script src="jquery.js" type="text/javascript" charset="utf-8"></script>
79
+ <script src="chain.js" type="text/javascript" charset="utf-8"></script>
80
+ <script src="bowline.js" type="text/javascript" charset="utf-8"></script>
81
+ <script type="text/javascript" charset="utf-8">
82
+ jQuery(function($){
83
+ // Bind the element users to UserBinder
84
+ var users = $('#users').bowline('users', function(){
85
+ var self = $(this);
86
+ self.find('.destroy').click(function(){
87
+ self.invoke('destroy');
88
+ return false;
89
+ })
90
+ });
91
+
92
+ $('#showAdmins').click(function(){
93
+ users.invoke('admins');
94
+ return false;
95
+ });
96
+
97
+ // Populate with all the users
98
+ users.invoke('index');
99
+ });
100
+ </script>
101
+ </head>
102
+ <body>
103
+ <div id="users">
104
+ <div class="item">
105
+ <span class="name"></span>
106
+ <span class="email"></span>
107
+ <a href="#" class="destroy">Delete</a>
108
+ </div>
109
+ </div>
110
+
111
+ <a href="#" id="showAdmins">Show admins</a>
112
+ </body>
113
+ </html>
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/bowline'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('bowline', Bowline::VERSION) do |p|
7
+ p.developer('Alex MacCaw', 'info@eribium.org')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.rubyforge_name = 'maccman'
10
+ p.extra_deps << ['templater', '>=0.3.2']
11
+ p.extra_dev_deps = [
12
+ ['newgem', ">= #{::Newgem::VERSION}"]
13
+ ]
14
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
15
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
16
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
17
+ p.rsync_args = '-av --delete --ignore-errors'
18
+ end
19
+
20
+ require 'newgem/tasks' # load /tasks/*.rake
21
+ Dir['tasks/**/*.rake'].each { |t| load t }
22
+
23
+ # TODO - want other tests/tasks run by default? Add them to the list
24
+ # task :default => [:spec, :features]
@@ -0,0 +1,96 @@
1
+ (function($){
2
+ $.bowline = {
3
+ setup: function(name, el){
4
+ var rb = eval("bowline_" + name + "_setup");
5
+ if(!rb) throw 'Unknown class';
6
+ rb(el);
7
+ },
8
+
9
+ klass: function(name){
10
+ var rb = eval("bowline_" + name);
11
+ if(!rb) throw 'Unknown class';
12
+ return rb;
13
+ },
14
+
15
+ instance: function(name, el){
16
+ var rb = eval("bowline_" + name + "_instance");
17
+ if(!rb) throw 'Unknown class';
18
+ return rb(el);
19
+ },
20
+
21
+ setupForms: function(){
22
+ // $('form').bind('submit', function(e){
23
+ // var src = $(this).attr('src').split('.');
24
+ // var rb = $.bowline.klass[src[0]];
25
+ // rb.params = $(this).serialize();
26
+ // rb.send(src[1]);
27
+ // return false;
28
+ // });
29
+ },
30
+
31
+ // A lot of JS libs require hashes
32
+ // without any functions in them
33
+ rubyHash: function( hsh ) {
34
+ res = {};
35
+ $.each(hsh, function(key, value){
36
+ if(typeof(value) != 'function'){
37
+ res[key] = value;
38
+ }
39
+ });
40
+ return res;
41
+ },
42
+
43
+ // Length on a Ruby array is a function
44
+ rubyMap: function( elems, callback ) {
45
+ var ret = [];
46
+
47
+ for ( var i = 0, length = elems.length(); i < length; i++ ) {
48
+ var value = callback( elems[ i ], i );
49
+
50
+ if ( value != null )
51
+ ret[ ret.length ] = value;
52
+ }
53
+
54
+ return ret.concat.apply( [], ret );
55
+ }
56
+ },
57
+
58
+ $.fn.bowline = function(name, options){
59
+ $(this).chain(options);
60
+ $.bowline.setup(name, $(this));
61
+ $(this).data('bowline', name);
62
+ $(this).trigger('setup:bowline');
63
+ return this;
64
+ };
65
+
66
+ $.fn.invoke = function(){
67
+ if($(this).chain('active')){
68
+ if($(this).data('bowline')){
69
+ // Class method
70
+ var name = $(this).data('bowline');
71
+ var func = $.bowline.klass(name);
72
+ } else {
73
+ // Instance method
74
+ var name = $(this).item('root').data('bowline');
75
+ var func = $.bowline.instance(name, $(this));
76
+ }
77
+ return func.apply(func, arguments);
78
+ } else {
79
+ throw 'Chain not active';
80
+ }
81
+ };
82
+
83
+ $.fn.updateCollection = function( items ){
84
+ items = $.bowline.rubyMap(items, function(n){
85
+ return $.bowline.rubyHash(n);
86
+ });
87
+ $(this).items('replace', items);
88
+ $(this).trigger('update:bowline');
89
+ };
90
+
91
+ $.fn.updateSingleton = function( item ){
92
+ item = $.bowline.rubyHash(item);
93
+ $(this).item('replace', item);
94
+ $(this).trigger('update:bowline');
95
+ };
96
+ })(jQuery)
@@ -0,0 +1,2348 @@
1
+ /**
2
+ * Chain.js
3
+ * jQuery Plugin for Data Binding
4
+ *
5
+ * Copyright (c) 2008 Rizqi Ahmad
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+
27
+ /* core.js */
28
+ (function($){
29
+
30
+ /**
31
+ * Chain Namespace
32
+ *
33
+ * @alias jQuery.Chain
34
+ * @namespace
35
+ */
36
+ $.Chain =
37
+ {
38
+ /**
39
+ * Version Number
40
+ *
41
+ * @alias jQuery.Chain.version
42
+ * @property {String}
43
+ */
44
+ version: '0.2',
45
+
46
+ /**
47
+ * Tag for use in @jQuery.Chain.parse@ (which is used in CustomUpdater).
48
+ * It is can be altered.
49
+ *
50
+ * @alias jQuery.Chain.tags
51
+ *
52
+ * @property {Array}
53
+ *
54
+ * @see jQuery.Chain.parse
55
+ */
56
+ tag: ['{', '}'],
57
+
58
+ /**
59
+ * Namespace containing all defined services.
60
+ *
61
+ * @namespace
62
+ *
63
+ * @alias jQuery.Chain.services
64
+ */
65
+ services: {},
66
+
67
+ /**
68
+ * Register a service to the service manager.
69
+ *
70
+ * @alias jQuery.Chain.service
71
+ *
72
+ * @param {String} name Service Name
73
+ * @param {Object} proto Service Object Prototype
74
+ *
75
+ * @example Create a Custom Service
76
+ * $.Chain.service('test', {
77
+ * // Default command handler
78
+ * handler: function(option)
79
+ * {
80
+ * // do something
81
+ * },
82
+ * // $(selector).test('what', somearg)
83
+ * $what: function(somearg)
84
+ * {
85
+ * // do something
86
+ * }
87
+ * });
88
+ *
89
+ * $('#element').test();
90
+ *
91
+ * @see jQuery.Chain.extend
92
+ */
93
+ service: function(name, proto)
94
+ {
95
+ this.services[name] = proto;
96
+
97
+ // Creating jQuery fn module with the service
98
+ $.fn[name] = function(options)
99
+ {
100
+ if(!this.length)
101
+ {return this;}
102
+
103
+ // Create Chain instance
104
+ var instance = this.data('chain-'+name);
105
+
106
+ // Extract arguments
107
+ var args = Array.prototype.slice.call(arguments, 1);
108
+
109
+ // Create Instance if it doesn't already exist
110
+ if(!instance)
111
+ {
112
+ // Return immediately if destroyed is called before Instance is created
113
+ if(options == 'destroy')
114
+ {return this;}
115
+ // Create Instance
116
+ instance = $.extend({element: this}, $.Chain.services[name]);
117
+ this.data('chain-'+name, instance);
118
+ // Initialize if possible
119
+ if(instance.init)
120
+ {instance.init();}
121
+ }
122
+
123
+ var result;
124
+
125
+ // Check whether to execute a command
126
+ if(typeof options == 'string' && instance['$'+options])
127
+ {result = instance['$'+options].apply(instance, args);}
128
+
129
+ // Otherwise try to execute default handler
130
+ else if(instance['handler'])
131
+ {result = instance['handler'].apply(instance, [options].concat(args));}
132
+
133
+ // Otherwise do nothing
134
+ else
135
+ {result = this;}
136
+
137
+ // Remove instance on destroy
138
+ if(options == 'destroy')
139
+ {this.removeData('chain-'+name);}
140
+
141
+ return result;
142
+ };
143
+ },
144
+
145
+ /**
146
+ * Extends service functionalities.
147
+ *
148
+ * @alias jQuery.Chain.extend
149
+ *
150
+ * @param {String} name Service Name
151
+ * @param {Object} proto Service Object Prototype
152
+ *
153
+ * @see jQuery.Chain.service
154
+ */
155
+ extend: function(name, proto)
156
+ {
157
+ if(this.services[name])
158
+ {this.services[name] = $.extend(this.services[name], proto);}
159
+ },
160
+
161
+ /**
162
+ * Check whether it is a jQuery Object
163
+ *
164
+ * @alias jQuery.Chain.jobject
165
+ *
166
+ * @param {Object} obj Object to be checked
167
+ *
168
+ * @example Using @jobject@
169
+ * $.Chain.jobject($()) // returns true
170
+ * $.Chain.jobject("test") // returns false
171
+ *
172
+ * @return {Boolean} True or False
173
+ *
174
+ * @see jQuery.Chain.jindentic
175
+ */
176
+ jobject: function(obj)
177
+ {
178
+ return obj && obj.init == $.fn.init;
179
+ },
180
+
181
+ /**
182
+ * Check whether two jQuery Collection identic
183
+ *
184
+ * @alias jQuery.Chain.jidentic
185
+ *
186
+ * @param {Object} j1 jQuery Object
187
+ * @param {Object} j2 jQuery Object
188
+ *
189
+ * @example Using @jidentic@
190
+ * a = $('div');
191
+ * b = $('div');
192
+ * c = $('div.test');
193
+ *
194
+ * (a == b) //returns false
195
+ *
196
+ * $.Chain.jidentic(a, b) // returns true
197
+ * $.Chain.jidentic(a, c) // returns false
198
+ *
199
+ * @return {Boolean} True or False
200
+ *
201
+ * @see jQuery.Chain.jobject
202
+ */
203
+ jidentic: function(j1, j2)
204
+ {
205
+ if(!j1 || !j2 || j1.length != j2.length)
206
+ {return false;}
207
+
208
+ var a1 = j1.get();
209
+ var a2 = j2.get();
210
+
211
+ for(var i=0; i<a1.length; i++)
212
+ {
213
+ if(a1[i] != a2[i])
214
+ {return false;}
215
+ }
216
+
217
+ return true;
218
+
219
+ },
220
+
221
+ /**
222
+ * Parse string contained @{something}@ to a Function
223
+ * that when executed replace those with the data it refers to.
224
+ * You can change the @{}@ tag by modifying @jQuery.Chain.tag@
225
+ *
226
+ * @param {String} text String
227
+ *
228
+ * @example Using @
229
+ * var fn = $.Chain.parse("My name is {first} {last}");
230
+ * fn({first:'Rizqi', last:'Ahmad'}) // returns "My name is Rizqi Ahmad"
231
+ *
232
+ * @return {Function} template string.
233
+ *
234
+ * @see jQuery.Chain.tag
235
+ */
236
+ parse: function()
237
+ {
238
+ var $this = {};
239
+ // Function Closure
240
+ $this.closure =
241
+ [
242
+ 'function($data, $el){'
243
+ +'var $text = [];\n'
244
+ +'$text.print = function(text)'
245
+ +'{this.push((typeof text == "number") ? text : ((typeof text != "undefined") ? text : ""));};\n'
246
+ +'with($data){\n',
247
+
248
+ '}\n'
249
+ +'return $text.join("");'
250
+ +'}'
251
+ ];
252
+
253
+ // Print text template
254
+ $this.textPrint = function(text)
255
+ {
256
+ return '$text.print("'
257
+ +text.split('\\').join('\\\\').split("'").join("\\'").split('"').join('\\"')
258
+ +'");';
259
+ };
260
+
261
+ // Print script template
262
+ $this.scriptPrint = function(text)
263
+ {
264
+ return '$text.print('+text+');';
265
+ };
266
+
267
+ $this.parser = function(text){
268
+ var tag = $.Chain.tag;
269
+
270
+ var opener, closer, closer2 = null, result = [];
271
+
272
+ while(text){
273
+
274
+ // Check where the opener and closer tag
275
+ // are located in the text.
276
+ opener = text.indexOf(tag[0]);
277
+ closer = opener + text.substring(opener).indexOf(tag[1]);
278
+
279
+ // If opener tag exists, otherwise there are no tags anymore
280
+ if(opener != -1)
281
+ {
282
+ // Handle escape. Tag can be escaped with '\\'.
283
+ // If tag is escaped. it will be handled as a normal text
284
+ // Otherwise it will be handled as a script
285
+ if(text[opener-1] == '\\')
286
+ {
287
+ closer2 = opener+tag[0].length + text.substring(opener+tag[0].length).indexOf(tag[0]);
288
+ if(closer2 != opener+tag[0].length-1 && text[closer2-1] == '\\')
289
+ {closer2 = closer2-1;}
290
+ else if(closer2 == opener+tag[0].length-1)
291
+ {closer2 = text.length;}
292
+
293
+ result.push($this.textPrint(text.substring(0, opener-1)));
294
+ result.push($this.textPrint(text.substring(opener, closer2)));
295
+ }
296
+ else
297
+ {
298
+ closer2 = null;
299
+ if(closer == opener-1)
300
+ {closer = text.length;}
301
+
302
+ result.push($this.textPrint(text.substring(0, opener)));
303
+ result.push($this.scriptPrint(text.substring(opener+tag[0].length, closer)));
304
+ }
305
+
306
+ text = text.substring((closer2 === null) ? closer+tag[1].length : closer2);
307
+ }
308
+ // If there are still text, it will be pushed to array
309
+ // So we won't stuck in an infinite loop
310
+ else if(text)
311
+ {
312
+ result.push($this.textPrint(text));
313
+ text = '';
314
+ }
315
+ }
316
+
317
+ return result.join('\n');
318
+ };
319
+
320
+
321
+ /*
322
+ * Real function begins here.
323
+ * We use closure for private variables and function.
324
+ */
325
+ return function($text)
326
+ {
327
+ var $fn = function(){};
328
+ try
329
+ {
330
+ eval('$fn = '+ $this.closure[0]+$this.parser($text)+$this.closure[1]);
331
+ }
332
+ catch(e)
333
+ {
334
+ throw "Parsing Error";
335
+ }
336
+
337
+ return $fn;
338
+ };
339
+ }()
340
+ };
341
+
342
+ })(jQuery);
343
+
344
+ /* update.js */
345
+ /**
346
+ * Chain Update Service
347
+ *
348
+ * @alias update
349
+ *
350
+ * @syntax $(selector).update(parameters);
351
+ */
352
+
353
+ (function($){
354
+
355
+ /**
356
+ * Chain Update Service Object - Providing methods of @update@.
357
+ * All method listed here can only be used internally
358
+ * using @jQuery.Chain.service@ or @jQuery.Chain.extend@
359
+ *
360
+ * @namespace
361
+ *
362
+ * @alias jQuery.Chain.services.update
363
+ *
364
+ * @see jQuery.Chain.service
365
+ * @see jQuery.Chain.extend
366
+ */
367
+
368
+ $.Chain.service('update', {
369
+ /**
370
+ * Default Handler
371
+ *
372
+ * @alias jQuery.Chain.services.update.handler
373
+ *
374
+ * @see jQuery.Chain.service
375
+ * @see jQuery.Chain.services.update.bind
376
+ * @see jQuery.Chain.services.update.trigger
377
+ */
378
+ handler: function(opt)
379
+ {
380
+ if(typeof opt == 'function')
381
+ {return this.bind(opt);}
382
+ else
383
+ {return this.trigger(opt);}
384
+ },
385
+
386
+ /**
387
+ * If you pass a function to update, it will bind it to the update event.
388
+ * just like jQuerys @click()@ or @mouseover()@.
389
+ *
390
+ * @alias update(fn)
391
+ * @alias jQuery.Chain.services.update.bind
392
+ *
393
+ * @param {Function} fn Listener
394
+ *
395
+ * @example
396
+ * // assuming #person is already chained
397
+ * $('#person').update(function{
398
+ * alert($(this).item().name);
399
+ * });
400
+ *
401
+ * $('#person').item({name: 'Rizqi'})
402
+ *
403
+ * @return {Object} jQuery Object
404
+ *
405
+ * @see jQuery.Chain.services.update.handler
406
+ */
407
+ bind: function(fn)
408
+ {
409
+ return this.element.bind('update', fn);
410
+ },
411
+
412
+ /**
413
+ * If no argument or "hard" is passed,
414
+ * it will update the element and trigger the update event.
415
+ *
416
+ * @alias update(opt)
417
+ * @alias jQuery.Chain.services.update.trigger
418
+ *
419
+ * @param {String} opt If 'hard', it will update each of items
420
+ *
421
+ * @example
422
+ * $('#person').update();
423
+ *
424
+ * @return {Object} jQuery Object
425
+ *
426
+ * @see jQuery.Chain.services.update.handler
427
+ */
428
+ trigger: function(opt)
429
+ {
430
+ this.element.items('update');
431
+ this.element.item('update');
432
+
433
+ this.element.triggerHandler('preupdate', this.element.item());
434
+
435
+ if(opt == 'hard')
436
+ {this.element.items(true).each(function(){$(this).update();});}
437
+
438
+ this.element.triggerHandler('update', this.element.item());
439
+
440
+ return this.element;
441
+ }
442
+ });
443
+
444
+ })(jQuery);
445
+
446
+ /* chain.js */
447
+ /**
448
+ * Chain Binding Service.
449
+ * Method to activate the chaining / element rendering service.
450
+ *
451
+ * @alias chain
452
+ *
453
+ * @syntax $(selector).chain(parameters);
454
+ */
455
+
456
+ (function($){
457
+
458
+ /**
459
+ * Chain Binding Service Object - Providing methods of @chain@.
460
+ * All method listed here can only be used internally
461
+ * using @jQuery.Chain.service@ or @jQuery.Chain.extend@
462
+ *
463
+ * @namespace
464
+ *
465
+ * @alias jQuery.Chain.services.chain
466
+ *
467
+ * @see jQuery.Chain.service
468
+ * @see jQuery.Chain.extend
469
+ */
470
+
471
+ $.Chain.service('chain', {
472
+ /**
473
+ * Initializer. Executed once at the first time @chain@ invoked.
474
+ *
475
+ * @alias jQuery.Chain.services.chain.init
476
+ *
477
+ * @see jQuery.Chain.service
478
+ */
479
+ init: function()
480
+ {
481
+ this.anchor = this.element;
482
+ this.template = this.anchor.html();
483
+ this.tplNumber = 0; // At Default it uses the first template.
484
+ this.builder = this.createBuilder();
485
+ this.plugins = {};
486
+ this.isActive = false;
487
+ this.destroyers = [];
488
+
489
+ // Add class 'chain-element' as identifier
490
+ this.element.addClass('chain-element');
491
+ },
492
+
493
+ /**
494
+ * Default handler.
495
+ *
496
+ * @alias jQuery.Chain.services.chain.handler
497
+ *
498
+ * @param {Object} obj Object to be handled
499
+ *
500
+ * @return {Object} jQuery Object
501
+ *
502
+ * @see jQuery.Chain.service
503
+ * @see jQuery.Chain.services.chain.handleUpdater
504
+ * @see jQuery.Chain.services.chain.handleBuilder
505
+ */
506
+ handler: function(obj, bool)
507
+ {
508
+ // Backup items and item, all items will be stored in Buffer
509
+ this.element.items('backup');
510
+ this.element.item('backup');
511
+
512
+ if(typeof obj == 'object')
513
+ {this.handleUpdater(obj);}
514
+ else if(typeof obj == 'function')
515
+ {this.handleBuilder(obj, bool);}
516
+
517
+ // Empty element, if @item@ it will filled again later
518
+ this.anchor.empty();
519
+
520
+ this.isActive = true;
521
+ this.element.update();
522
+
523
+ return this.element;
524
+ },
525
+
526
+ /**
527
+ * Updater Handler.
528
+ * If you pass an object to @chain@, it will treated as a updater object.
529
+ * The updater is a hash of selector and value string:
530
+ * like @chain({'my css selector': 'My Content String'})@
531
+ * or @chain({'my css selector': {attributes}})@
532
+ *
533
+ * @alias chain(updater)
534
+ * @alias jQuery.Chain.services.chain.handleUpdater
535
+ *
536
+ * @param {Object} rules Updater rules to be parsed
537
+ *
538
+ * @example Usage
539
+ * $(selector)
540
+ * .chain({
541
+ * // Items anchor, where the Item iteration should be placed
542
+ * anchor: anchor,
543
+ * // If true, the default updater is overridden
544
+ * override: false,
545
+ * // Use custom builder
546
+ * builder: function(){},
547
+ * // Update the element self
548
+ * self: "This is my {data}",
549
+ * // Use css selector to update child element
550
+ * '.element.selector': "Using String Updater",
551
+ * // Use Function as updater
552
+ * '.element.selector': function(data, el){},
553
+ * // Updating Attributes
554
+ * '.element.selector': {
555
+ * attribute1: "{attribute}",
556
+ * className: "{className}",
557
+ * content: "This is the {content}",
558
+ * value: "This is the {value}"
559
+ * }
560
+ * });
561
+ *
562
+ * @example Using Default Updater
563
+ * $('<div><span class="name">Name</span></div>')
564
+ * .item({name: 'Steve Jobs'})
565
+ * .chain()
566
+ * .appendTo(document.body);
567
+ *
568
+ * @example Using Custom Updater
569
+ * $('<div><div class="name"><span class="first">First</span> <span class="last">Last</span></div></div>')
570
+ * .item({first:'Steve', last:'Jobs'})
571
+ * .chain({
572
+ * '.name .first': {
573
+ * style: 'color: blue;',
574
+ * content: 'First Name: {first}'
575
+ * },
576
+ * '.name .last': 'Family Name: {last}'
577
+ * })
578
+ * .appendTo(document.body);
579
+ *
580
+ * @example Attach Builder Inside Updater
581
+ * $('<div><div class="name">Name</div><div class="address">Address</div></div>')
582
+ * .item({name:'Steve Jobs', address:'Cupertino'})
583
+ * .chain({
584
+ * builder: function(){
585
+ * var data = this.item();
586
+ * this.find('.name').click(function(){alert(data.name)});
587
+ * this.find('.address').mouseout(function(){alert(data.address)});
588
+ * },
589
+ * '.name': '{name}',
590
+ * '.address': '{address}'
591
+ * })
592
+ * .appendTo(document.body);
593
+ */
594
+ handleUpdater: function(rules)
595
+ {
596
+ // Extract Builder
597
+ var builder = rules.builder;
598
+ delete rules.builder;
599
+
600
+ // Extract Options
601
+ this.options = rules.options || {};
602
+ delete rules.options;
603
+
604
+ // Extract Anchor
605
+ if(rules.anchor)
606
+ {this.setAnchor(rules.anchor);}
607
+ delete rules.anchor;
608
+
609
+ // Extract Override
610
+ var override = rules.override;
611
+ delete rules.override;
612
+
613
+ for(var i in rules)
614
+ {
615
+ // Parse String to Function
616
+ if(typeof rules[i] == 'string')
617
+ {
618
+ rules[i] = $.Chain.parse(rules[i]);
619
+ }
620
+ // Parse Attributes Object to Functions
621
+ else if(typeof rules[i] == 'object')
622
+ {
623
+ for(var j in rules[i])
624
+ {
625
+ if(typeof rules[i][j] == 'string')
626
+ {
627
+ rules[i][j] = $.Chain.parse(rules[i][j]);
628
+ }
629
+ }
630
+ }
631
+ }
632
+
633
+ // Create Updater
634
+ var fn = function(event, data)
635
+ {
636
+ var el, val;
637
+ var self = $(this);
638
+ for(var i in rules)
639
+ {
640
+ // If self, update the element itself
641
+ if(i == 'self')
642
+ {el = self;}
643
+ // Otherwise find element inside self
644
+ else
645
+ {el = $(i, self);}
646
+
647
+ // Executing
648
+ // If no attributes, put the result to html (value if input)
649
+ if (typeof rules[i] == 'function')
650
+ {
651
+ val = rules[i].apply(self, [data, el]);
652
+ if(typeof val == 'string')
653
+ {el.not(':input').html(val).end().filter(':input').val(val);}
654
+ }
655
+ // If attributes, then execute the function for each attr.
656
+ else if(typeof rules[i] == 'object')
657
+ {
658
+ for(var j in rules[i])
659
+ {
660
+ if (typeof rules[i][j] == 'function')
661
+ {
662
+ val = rules[i][j].apply(self, [data, el]);
663
+ if(typeof val == 'string')
664
+ {
665
+ // Some special attributes
666
+ if(j == 'content')
667
+ {el.html(val);}
668
+ else if(j == 'text')
669
+ {el.text(val);}
670
+ else if(j == 'value')
671
+ {el.val(val);}
672
+ else if(j == 'class' || j == 'className')
673
+ {el.addClass(val);}
674
+ // Otherwise fill attribute as normal
675
+ else
676
+ {el.attr(j, val);}
677
+ }
678
+
679
+ }
680
+ }
681
+ }
682
+ }
683
+ };
684
+
685
+ var defBuilder = this.defaultBuilder;
686
+
687
+ // Define Builder
688
+ this.builder = function(root)
689
+ {
690
+ if(builder)
691
+ {builder.apply(this, [root]);}
692
+
693
+ if(!override)
694
+ {defBuilder.apply(this);}
695
+
696
+ // Here goes the updater
697
+ this.update(fn);
698
+
699
+ // This prevent infinite recursion
700
+ // see: jQuery.Chain.services.item.build
701
+ return false;
702
+ };
703
+ },
704
+
705
+ /**
706
+ * Builder Handler.
707
+ * If you pass a function to @chain@, it will be handled
708
+ * as @{builder: function}@, enabling you to use the default
709
+ * updater while customizing the events etc.
710
+ *
711
+ * @alias chain(fn)
712
+ * @alias jQuery.Chain.services.chain.handleBuilder
713
+ *
714
+ * @param {Function} fn Builder Function
715
+ * @param {Boolean} bool If true, it just use the builder provided. Not creating new Builder
716
+ *
717
+ * @example
718
+ * $('<div><div class="name">Name</div><div class="address">Address</div></div>')
719
+ * .item({name:'Steve Jobs', address:'Cupertino'})
720
+ * .chain(function(){
721
+ * this.bind('click', function(){
722
+ * var data = this.item();
723
+ * alert('name:'+data.name+', address:'+data.address);
724
+ * });
725
+ *
726
+ * // if you return false, default builder wont be executed
727
+ * // You don't have to return true;
728
+ * return true;
729
+ * })
730
+ * .appendTo(document.body);
731
+ *
732
+ * @see jQuery.Chain.services.chain.handleUpdater
733
+ * @see jQuery.Chain.services.chain.createBuilder
734
+ */
735
+ handleBuilder: function(fn, bool)
736
+ {
737
+ if(bool)
738
+ {this.builder = fn;}
739
+ else
740
+ {this.builder = this.createBuilder(fn);}
741
+ },
742
+
743
+
744
+ /**
745
+ * Default Builder - Automatic Data filler
746
+ *
747
+ * @alias jQuery.Chain.services.chain.defaultBuilder
748
+ *
749
+ * @param {Function} builder Builder Function
750
+ * @param {Object} root Root Element Object
751
+ *
752
+ * @see jQuery.Chain.services.chain.createBuilder
753
+ */
754
+ defaultBuilder: function(builder, root)
755
+ {
756
+ // Caution:
757
+ // @this@ is in this function @this.element@
758
+
759
+ // if builder return false, res will be false
760
+ // Otherwise true
761
+ // Using this, the default updater can be disabled
762
+ var res = builder ? (builder.apply(this, [root]) !== false) : true;
763
+
764
+ // Default Updater
765
+ if(res)
766
+ {
767
+ this.bind('update', function(event, data){
768
+ var self = $(this);
769
+ // Iterate through data
770
+ // Find element with the same class as data property
771
+ // Insert data depending of elemen type
772
+ for(var i in data)
773
+ {
774
+ if(typeof data[i] != 'object' && typeof data[i] != 'function')
775
+ {
776
+ // This prevents selector to select inside nested chain-element
777
+ // Important to support recursion & nested element
778
+ // NEED OPTIMIZATION
779
+ self.find('> .'+i+', *:not(.chain-element) .'+i)
780
+ .each(function(){
781
+ var match = $(this);
782
+ if(match.filter(':input').length)
783
+ {match.val(data[i]);}
784
+ else if(match.filter('img').length)
785
+ {match.attr('src', data[i]);}
786
+ else
787
+ {match.html(data[i]);}
788
+ });
789
+ }
790
+ }
791
+ });
792
+ }
793
+ },
794
+
795
+ /**
796
+ * Builder Generator (Wrapper).
797
+ *
798
+ * @alias jQuery.Chain.services.chain.createBuilder
799
+ *
800
+ * @param {Function} builder Builder
801
+ *
802
+ * @return {Function} Wrapped Builder
803
+ *
804
+ * @see jQuery.Chain.services.chain.defaultBuilder;
805
+ */
806
+ createBuilder: function(builder)
807
+ {
808
+ var defBuilder = this.defaultBuilder;
809
+ return function(root){
810
+ defBuilder.apply(this, [builder, root]);
811
+ return false;
812
+ };
813
+ },
814
+
815
+ /**
816
+ * Set Anchor (Container for @items@ to be populated, default: @this.element@)
817
+ *
818
+ * @alias jQuery.Chain.services.chain.setAnchor
819
+ *
820
+ * @param {Object} anchor Anchor element
821
+ *
822
+ * @see jQuery.Chain.services.chain.$anchor
823
+ */
824
+ setAnchor: function(anchor)
825
+ {
826
+ this.anchor.html(this.template);
827
+ this.anchor = anchor == this.element ? anchor : this.element.find(anchor).eq(0);
828
+ this.template = this.anchor.html();
829
+ this.anchor.empty();
830
+ },
831
+
832
+ /**
833
+ * Set new Anchor and rerender if new anchor passed.
834
+ * Otherwise return current anchor.
835
+ *
836
+ * If you use @items()@ with @chain()@,
837
+ * you can use @chain('anchor', selector)@ to move the element,
838
+ * where the items will be generated.
839
+ *
840
+ * @alias chain('anchor')
841
+ * @alias jQuery.Chain.services.chain.$anchor
842
+ *
843
+ * @param {Object} anchor Anchor element or selector
844
+ *
845
+ * @return {Object} current element (if new Anchor passed), otherwise current anchor
846
+ *
847
+ * @example
848
+ * $('#persons').chain('anchor', '.wrapper');
849
+ *
850
+ * // Define Anchor directly while building
851
+ * $('#persons').items([...]).chain({anchor:'.wrapper', builder: ...});
852
+ */
853
+ $anchor: function(anchor)
854
+ {
855
+ if(anchor)
856
+ {
857
+ this.element.items('backup');
858
+ this.element.item('backup');
859
+
860
+ this.setAnchor(anchor);
861
+ this.element.update();
862
+
863
+ return this.element;
864
+ }
865
+ else
866
+ {
867
+ return this.anchor;
868
+ }
869
+ },
870
+
871
+ /**
872
+ * Getting/Switching Template.
873
+ *
874
+ * @alias chain('template')
875
+ * @alias jQuery.Chain.services.chain.$template
876
+ *
877
+ * @param {Number, String} arg Argument
878
+ *
879
+ * @return {Object} jQuery Object
880
+ *
881
+ * @example
882
+ * $(selector).chain('template') // Returns current Template (jQuery Object)
883
+ * $(selector).chain('template', 'raw') // Returns raw HTML Templates (all)
884
+ * $(selector).chain('template', nr) // Switch to template nr (read: Number)
885
+ * $(selector).chain('template', '.tree-column') // Switch by selector
886
+ */
887
+ $template: function(arg)
888
+ {
889
+ // Returns current Template (jQuery Object)
890
+ if(!arguments.length)
891
+ {return $('<div>').html(this.template).children().eq(this.tplNumber);}
892
+
893
+ // Returns raw HTML Template
894
+ if(arg == 'raw')
895
+ {return this.template;}
896
+
897
+ // Switch template by Number
898
+ if(typeof arg == 'number')
899
+ {
900
+ this.tplNumber = arg;
901
+ }
902
+ // Switch template by selector
903
+ else
904
+ {
905
+ var tpl = $('<div>').html(this.template).children();
906
+ var node = tpl.filter(arg).eq(0);
907
+
908
+ if(node.length)
909
+ {this.tplNumber = tpl.index(node);}
910
+ else
911
+ {return this.element;} // If not found do nothing
912
+ }
913
+
914
+ this.element.items('backup');
915
+ this.element.item('backup');
916
+ this.element.update();
917
+
918
+ return this.element;
919
+ },
920
+
921
+ /**
922
+ * Get/Change Builder.
923
+ * If you don't pass any argument, it will return the created builder.
924
+ *
925
+ * @alias chain('builder')
926
+ * @alias jQuery.Chain.services.chain.$builder
927
+ *
928
+ * @param {Function, Object} builder (Optional)
929
+ *
930
+ * @return {Function, Object} returns builder function, or jQuery Object depends on arg
931
+ *
932
+ * @example
933
+ * $('#el').chain('builder') // returns builder function
934
+ * $('#el').chain('builder', newBuilder) // Replace Builder
935
+ */
936
+ $builder: function(builder)
937
+ {
938
+ if(builder)
939
+ {return this.handler(builder);}
940
+ else
941
+ {return this.builder;}
942
+ },
943
+
944
+ /**
945
+ * Check status
946
+ *
947
+ * @alias chain('active')
948
+ * @alias jQuery.Chain.services.chain.$active
949
+ *
950
+ * @return {Boolean} true if active
951
+ */
952
+ $active: function()
953
+ {
954
+ return this.isActive;
955
+ },
956
+
957
+ /**
958
+ * Set/Get options
959
+ *
960
+ * @alias chain('options')
961
+ * @alias jQuery.Chain.services.chain.$options
962
+ *
963
+ * @param {String} opt Option name
964
+ * @param {Anything} val Option value
965
+ *
966
+ * @return {Object} if no value given, it returns the value, otherwise the element itself
967
+ */
968
+
969
+ $options: function(opt, val)
970
+ {
971
+ this.options = this.options || {};
972
+
973
+ if(arguments.length == 2)
974
+ {
975
+ this.options[opt] = val;
976
+ return this.element;
977
+ }
978
+
979
+ else
980
+ {
981
+ return this.options[opt];
982
+ }
983
+ },
984
+
985
+ /**
986
+ * Add/Remove Plugins that extend builder
987
+ *
988
+ * @alias chain('plugin')
989
+ * @alias jQuery.Chain.services.chain.$plugin
990
+ *
991
+ * @param {String} name Plugin Name
992
+ * @param {Function, Boolean} fn Plugin Function / False to remove
993
+ *
994
+ * @return {Object} jQuery Object
995
+ */
996
+ $plugin: function(name, fn)
997
+ {
998
+ if(fn === null)
999
+ {delete this.plugins[name];}
1000
+ else if(typeof fn == 'function')
1001
+ {this.plugins[name] = fn;}
1002
+ else if(name && !fn)
1003
+ {return this.plugins[name];}
1004
+ else
1005
+ {return this.plugins;}
1006
+
1007
+ if(typeof fn == 'function')
1008
+ {
1009
+ this.element.items(true).each(function(){
1010
+ var self = $(this);
1011
+ fn.call(self, self.item('root'));
1012
+ });
1013
+ }
1014
+
1015
+ this.element.update();
1016
+
1017
+ return this.element;
1018
+ },
1019
+
1020
+ /**
1021
+ * Clone Element unchained, with ID removed.
1022
+ *
1023
+ * @alias chain('clone')
1024
+ * @alias jQuery.Chain.services.chain.$clone
1025
+ *
1026
+ * @return {Object} jQuery Object containing cloned Element
1027
+ */
1028
+ $clone: function()
1029
+ {
1030
+ var id = this.element.attr('id');
1031
+ this.element.attr('id', '');
1032
+
1033
+ var clone = this.element.clone().empty().html(this.template);
1034
+ this.element.attr('id', id);
1035
+
1036
+ return clone;
1037
+ },
1038
+
1039
+ /**
1040
+ * Destroy Chain, restore Element to previous condition.
1041
+ *
1042
+ * @alias chain('destroy')
1043
+ * @alias jQuery.Chain.services.chain.$destroy
1044
+ *
1045
+ * @param {Boolean} nofollow If true, it won't destroy nested chain elements
1046
+ *
1047
+ * @return {Object} jQuery Object
1048
+ */
1049
+ $destroy: function(nofollow)
1050
+ {
1051
+ this.element.removeClass('chain-element');
1052
+
1053
+ if(!nofollow)
1054
+ {
1055
+ // Backup to buffer
1056
+ this.element.items('backup');
1057
+ this.element.item('backup');
1058
+
1059
+ // Destroy nested elements
1060
+ this.element.find('.chain-element').each(function(){
1061
+ $(this).chain('destroy', true);
1062
+ });
1063
+ }
1064
+
1065
+ // Trigger destroy event
1066
+ this.element.triggerHandler('destroy');
1067
+
1068
+ this.isActive = false;
1069
+
1070
+ // Restore HTML
1071
+ this.anchor.html(this.template);
1072
+
1073
+ return this.element;
1074
+ }
1075
+ });
1076
+
1077
+ })(jQuery);
1078
+
1079
+ /* item.js */
1080
+ /**
1081
+ * Chain Item Service.
1082
+ * Method to bind item to object.
1083
+ *
1084
+ * @alias item
1085
+ *
1086
+ * @syntax $(selector).item(parameters);
1087
+ */
1088
+
1089
+ (function($){
1090
+
1091
+ /**
1092
+ * Chain Item Manager - Providing methods of @item@.
1093
+ * All method listed here can only be used internally
1094
+ * using @jQuery.Chain.service@ or @jQuery.Chain.extend@
1095
+ *
1096
+ * @namespace
1097
+ *
1098
+ * @alias jQuery.Chain.services.item
1099
+ *
1100
+ * @see jQuery.Chain.service
1101
+ * @see jQuery.Chain.extend
1102
+ */
1103
+
1104
+ $.Chain.service('item', {
1105
+ /**
1106
+ * Initializer. Executed once at the first time @item@ invoked.
1107
+ *
1108
+ * @alias jQuery.Chain.services.item.init
1109
+ *
1110
+ * @see jQuery.Chain.service
1111
+ */
1112
+ init: function()
1113
+ {
1114
+ this.isActive = false;
1115
+ this.isBuilt = false;
1116
+ this.root = this.element;
1117
+ this.data = false;
1118
+ this.datafn = this.dataHandler;
1119
+ },
1120
+
1121
+ /**
1122
+ * Default handler.
1123
+ *
1124
+ * @alias jQuery.Chain.services.item.handler
1125
+ *
1126
+ * @param {Object} obj Object to be handled
1127
+ *
1128
+ * @return {Object} jQuery Object
1129
+ *
1130
+ * @see jQuery.Chain.service
1131
+ * @see jQuery.Chain.services.item.handleObject
1132
+ * @see jQuery.Chain.services.item.handleFunction
1133
+ * @see jQuery.Chain.services.item.handleDefault
1134
+ */
1135
+ handler: function(obj)
1136
+ {
1137
+ if(typeof obj == 'object')
1138
+ {return this.handleObject(obj);}
1139
+ else if(typeof obj == 'function')
1140
+ {return this.handleFunction(obj);}
1141
+ else
1142
+ {return this.handleDefault();}
1143
+ },
1144
+
1145
+ /**
1146
+ * Edit/Bind Item.
1147
+ * If no Object defined, it will bind the object to the Item, otherwise
1148
+ * it will alter the object using the provided object.
1149
+ *
1150
+ * @alias item(object)
1151
+ * @alias jQuery.Chain.services.item.handleObject
1152
+ *
1153
+ * @param {Object} obj Object to be inserted
1154
+ *
1155
+ * @return {Object} jQuery Object
1156
+ *
1157
+ * @example
1158
+ * $('#element').item({name:'Rizqi', country:'Germany'});
1159
+ * $('#element').item({country:'Indonesia'});
1160
+ * $('#element').item(); // Returns {name:'Rizqi', country:'Indonesia'}
1161
+ *
1162
+ * @see jQuery.Chain.services.item.handler
1163
+ */
1164
+ handleObject: function(obj)
1165
+ {
1166
+ this.setData(obj);
1167
+ this.isActive = true;
1168
+ this.update();
1169
+
1170
+ return this.element;
1171
+ },
1172
+
1173
+ /**
1174
+ * Add setter and getter to item.
1175
+ * This function will change the way @item(object)@ and @item()@ works.
1176
+ *
1177
+ * @alias item(fn)
1178
+ * @alias jQuery.Chain.services.item.handleFunction
1179
+ *
1180
+ * @param {Function} fn Getter&Setter Function
1181
+ *
1182
+ * @return {Object} jQuery Object
1183
+ *
1184
+ * @example
1185
+ * $(element).item(function(oldval, newval){
1186
+ * //setter
1187
+ * if(newval)
1188
+ * return $.extend(oldval, newval);
1189
+ * //getter
1190
+ * else
1191
+ * return oldval;
1192
+ * })
1193
+ */
1194
+ handleFunction: function(fn)
1195
+ {
1196
+ // datafn stores the getter/setter function
1197
+ this.datafn = fn;
1198
+
1199
+ return this.element;
1200
+ },
1201
+
1202
+ /**
1203
+ * Get Data if no argument passed.
1204
+ *
1205
+ * @alias item()
1206
+ * @alias jQuery.Chain.services.item.handleDefault
1207
+ *
1208
+ * @return {Object, Boolean} Returns Data Object if exist, otherwise false
1209
+ */
1210
+ handleDefault: function()
1211
+ {
1212
+ if(this.isActive)
1213
+ {return this.getData();}
1214
+ else
1215
+ {return false;}
1216
+ },
1217
+
1218
+ /**
1219
+ * Data Getter Wrapper Function
1220
+ *
1221
+ * @alias jQuery.Chain.services.item.getData
1222
+ *
1223
+ * @return {Object} data
1224
+ */
1225
+ getData: function()
1226
+ {
1227
+ // Call Getter
1228
+ this.data = this.datafn.call(this.element, this.data);
1229
+
1230
+ return this.data;
1231
+ },
1232
+
1233
+ /**
1234
+ * Data Setter Wrapper Function
1235
+ *
1236
+ * @alias jQuery.Chain.services.item.setData
1237
+ */
1238
+ setData: function(obj)
1239
+ {
1240
+ var data;
1241
+
1242
+ // Determine whether object is a jQuery object or a data object
1243
+ if($.Chain.jobject(obj) && obj.item())
1244
+ {data = $.extend({}, obj.item());}
1245
+ else if($.Chain.jobject(obj))
1246
+ {data = {};}
1247
+ else
1248
+ {data = obj;}
1249
+
1250
+ // Call Setter
1251
+ this.data = this.datafn.call(this.element, this.data || data, data);
1252
+
1253
+ // Handle Linked Element
1254
+ if(this.linkElement && this.linkElement[0] != obj[0])
1255
+ {
1256
+ var el = this.linkFunction();
1257
+ if($.Chain.jobject(el) && el.length && el.item())
1258
+ {el.item(this.data);}
1259
+ }
1260
+ },
1261
+
1262
+ /**
1263
+ * Default Getter/Setter
1264
+ *
1265
+ * @alias jQuery.Chain.services.item.dataHandler
1266
+ *
1267
+ * @param {Object} oldval Old value
1268
+ * @param {Object} newval New Value
1269
+ *
1270
+ * @return {Object} returns data value
1271
+ */
1272
+ dataHandler: function(oldval, newval)
1273
+ {
1274
+ if(arguments.length == 2)
1275
+ {return $.extend(oldval, newval);}
1276
+ else
1277
+ {return oldval;}
1278
+ },
1279
+
1280
+ /**
1281
+ * Update element. Wrapper for @jQuery.Chain.services.item.element.update@
1282
+ *
1283
+ * @alias jQuery.Chain.services.item.update
1284
+ *
1285
+ * @return {Object} jQuery Object
1286
+ */
1287
+ update: function()
1288
+ {
1289
+ return this.element.update();
1290
+ },
1291
+
1292
+ /**
1293
+ * Build item, apply builder and plugins
1294
+ *
1295
+ * @alias jQuery.Chain.services.item.build
1296
+ *
1297
+ * @see jQuery.Chain.services.item.$update
1298
+ */
1299
+ build: function()
1300
+ {
1301
+ // IE Fix
1302
+ var fix = this.element.chain('template', 'raw').replace(/jQuery\d+\=\"null\"/gi, "");
1303
+ this.element.chain('anchor').html(fix);
1304
+
1305
+ // If item has root (items)
1306
+ if(!$.Chain.jidentic(this.root, this.element))
1307
+ {
1308
+ // Get plugin from root and apply them
1309
+ var plugins = this.root.chain('plugin');
1310
+ for(var i in plugins)
1311
+ {
1312
+ plugins[i].apply(this.element, [this.root]);
1313
+ }
1314
+
1315
+ }
1316
+
1317
+ // Apply builder
1318
+ this.element.chain('builder').apply(this.element, [this.root]);
1319
+ this.isBuilt = true;
1320
+ },
1321
+
1322
+ /**
1323
+ * Item Updater, called within @$(element).update()@
1324
+ *
1325
+ * @alias item('update')
1326
+ * @alias jQuery.Chain.services.item.$update
1327
+ *
1328
+ * @return {Object} jQuery Object
1329
+ */
1330
+ $update: function()
1331
+ {
1332
+ if(this.element.chain('active') && this.isActive && !this.isBuilt && this.getData())
1333
+ {this.build();}
1334
+
1335
+ return this.element;
1336
+ },
1337
+
1338
+ /**
1339
+ * Replace Data with new data
1340
+ *
1341
+ * @alias item('replace')
1342
+ * @alias jQuery.Chain.services.item.$replace
1343
+ *
1344
+ * @param {Object} obj Data Object
1345
+ *
1346
+ * @return {Object} jQuery Object
1347
+ *
1348
+ * @example
1349
+ * $(element).item('replace', data);
1350
+ */
1351
+ $replace: function(obj)
1352
+ {
1353
+ this.data = {};
1354
+ this.setData(obj);
1355
+ this.isActive = true;
1356
+ this.update();
1357
+ return this.element;
1358
+ },
1359
+
1360
+ /**
1361
+ * Remove Item And destroy it.
1362
+ *
1363
+ * @alias item('remove')
1364
+ * @alias jQuery.Chain.services.item.$remove
1365
+ *
1366
+ * @param {Boolean} noupdate If true it won't update the root element
1367
+ */
1368
+ $remove: function(noupdate)
1369
+ {
1370
+ // Destroy And Remove
1371
+ this.element.chain('destroy');
1372
+ this.element.remove();
1373
+ this.element.item('link', null);
1374
+ this.element.item('destroy');
1375
+
1376
+ // Update root under certain circumtances
1377
+ if(!$.Chain.jidentic(this.root, this.element) && !noupdate)
1378
+ {this.root.update();}
1379
+ },
1380
+
1381
+ /**
1382
+ * Check Status of @item@
1383
+ *
1384
+ * @alias item('active')
1385
+ * @alias jQuery.Chain.services.item.$active
1386
+ *
1387
+ * @return {Boolean} Status
1388
+ */
1389
+ $active: function()
1390
+ {
1391
+ return this.isActive;
1392
+ },
1393
+
1394
+ /**
1395
+ * Get/Set Root element.
1396
+ *
1397
+ * @alias item('root');
1398
+ * @alias jQuery.Chain.services.item.$root
1399
+ *
1400
+ * @param {Object} root New Root element
1401
+ *
1402
+ * @return {Object} If a new root passed, it will be item Element. Otherwise current root.
1403
+ */
1404
+ $root: function(root)
1405
+ {
1406
+ if(arguments.length)
1407
+ {
1408
+ this.root = root;
1409
+ this.update();
1410
+ return this.element;
1411
+ }
1412
+ else
1413
+ {
1414
+ return this.root;
1415
+ }
1416
+ },
1417
+
1418
+ /**
1419
+ * Backup Item to the state before being built.
1420
+ *
1421
+ * @alias item('backup')
1422
+ * @alias jQuery.Chain.services.item.$backup
1423
+ *
1424
+ * @return {Object} jQuery Object
1425
+ */
1426
+ $backup: function()
1427
+ {
1428
+ this.isBuilt = false;
1429
+
1430
+ return this.element;
1431
+ },
1432
+
1433
+ /**
1434
+ * Bind Item to other (chained) element. If one of them is updated,
1435
+ * the linked element will be updated.
1436
+ *
1437
+ * @alias item('link')
1438
+ * @alias jQuery.Chain.services.item.$link
1439
+ *
1440
+ * @param {Object} element element/selector to be linked with
1441
+ * @param {String} collection Collection to be linked with (has to be @"self"@ if linked to item)
1442
+ *
1443
+ * @return {Object} jQuery Element
1444
+ *
1445
+ * @see jQuery.Chain.services.items.collection
1446
+ */
1447
+ $link: function(element, collection)
1448
+ {
1449
+ // If there are previous linkElement
1450
+ if(this.linkElement)
1451
+ {
1452
+ this.linkElement.unbind('update', this.linkUpdater);
1453
+ this.linkElement = null;
1454
+ }
1455
+
1456
+ element = $(element);
1457
+ if(element.length)
1458
+ {
1459
+ var self = this;
1460
+ this.isActive = true;
1461
+ this.linkElement = element;
1462
+ // Function that get the linked item.
1463
+ this.linkFunction = function()
1464
+ {
1465
+ if(typeof collection == 'function')
1466
+ {
1467
+ try{
1468
+ return collection.call(self.element, self.linkElement);
1469
+ }catch(e){
1470
+ return $().eq(-1);
1471
+ }
1472
+ }
1473
+ else if(typeof collection == 'string')
1474
+ {
1475
+ return self.linkElement.items('collection', collection);
1476
+ }
1477
+ else
1478
+ {
1479
+ return $().eq(-1);
1480
+ }
1481
+ };
1482
+
1483
+ // Watch linked element for update, and trigger update in self
1484
+ this.linkUpdater = function()
1485
+ {
1486
+ var res = self.linkFunction();
1487
+ if(res && res.length)
1488
+ {self.element.item(res);}
1489
+ };
1490
+
1491
+ this.linkElement.bind('update', this.linkUpdater);
1492
+ this.linkUpdater();
1493
+ }
1494
+
1495
+ return this.element;
1496
+ },
1497
+
1498
+ /**
1499
+ * Destroy item service.
1500
+ *
1501
+ * @alias item('destroy')
1502
+ * @alias jQuery.Chain.services.item.$destroy
1503
+ *
1504
+ * @return {Object} jQuery Element
1505
+ */
1506
+ $destroy: function()
1507
+ {
1508
+ return this.element;
1509
+ }
1510
+ });
1511
+
1512
+ })(jQuery);
1513
+
1514
+ /* items.js */
1515
+ /**
1516
+ * Chain Items Service.
1517
+ * Method to bind items to object.
1518
+ *
1519
+ * @alias items
1520
+ *
1521
+ * @syntax $(selector).items(parameters);
1522
+ */
1523
+
1524
+ (function($){
1525
+
1526
+ /**
1527
+ * Chain Items Manager - Providing methods of @items@.
1528
+ * All method listed here can only be used internally
1529
+ * using @jQuery.Chain.service@ or @jQuery.Chain.extend@
1530
+ *
1531
+ * @namespace
1532
+ *
1533
+ * @alias jQuery.Chain.services.items
1534
+ *
1535
+ * @see jQuery.Chain.service
1536
+ * @see jQuery.Chain.extend
1537
+ */
1538
+
1539
+ $.Chain.service('items', {
1540
+ /**
1541
+ * Collection of Function for getting items
1542
+ *
1543
+ * @namespace
1544
+ * @alias jQuery.Chain.services.items.collections
1545
+ *
1546
+ * @see jQuery.Chain.services.items.collection
1547
+ */
1548
+ collections:
1549
+ {
1550
+ /**
1551
+ * Get all items, including hidden
1552
+ *
1553
+ * @alias jQuery.Chain.services.items.collections.all
1554
+ *
1555
+ * @return {Object} jQuery Object containing items
1556
+ */
1557
+ all: function()
1558
+ {
1559
+ return this.element.chain('anchor').children('.chain-item');
1560
+ },
1561
+
1562
+ /**
1563
+ * Get all visible items
1564
+ *
1565
+ * @alias jQuery.Chain.services.items.collections.visible
1566
+ *
1567
+ * @return {Object} jQuery Object containing items
1568
+ */
1569
+ visible: function()
1570
+ {
1571
+ return this.element.chain('anchor').children('.chain-item:visible');
1572
+ },
1573
+
1574
+ /**
1575
+ * Get all hidden items
1576
+ *
1577
+ * @alias jQuery.Chain.services.items.collections.hidden
1578
+ *
1579
+ * @return {Object} jQuery Object containing items
1580
+ */
1581
+ hidden: function()
1582
+ {
1583
+ return this.element.chain('anchor').children('.chain-item:hidden');
1584
+ },
1585
+
1586
+ /**
1587
+ * Get self
1588
+ *
1589
+ * @alias jQuery.Chain.services.items.collections.self
1590
+ *
1591
+ * @return {Object} jQuery Object of the element
1592
+ */
1593
+ self: function()
1594
+ {
1595
+ return this.element;
1596
+ }
1597
+ },
1598
+
1599
+ /**
1600
+ * Initializer. Executed once at the first time @items@ invoked.
1601
+ *
1602
+ * @alias jQuery.Chain.services.items.init
1603
+ *
1604
+ * @see jQuery.Chain.service
1605
+ */
1606
+ init: function()
1607
+ {
1608
+ this.isActive = false;
1609
+ this.pushBuffer = [];
1610
+ this.shiftBuffer = [];
1611
+ this.collections = $.extend({}, this.collections);
1612
+ },
1613
+
1614
+ /**
1615
+ * Default handler.
1616
+ *
1617
+ * @alias jQuery.Chain.services.items.handler
1618
+ *
1619
+ * @param {Object} obj Object to be handled
1620
+ *
1621
+ * @return {Object} jQuery Object
1622
+ *
1623
+ * @see jQuery.Chain.service
1624
+ * @see jQuery.Chain.services.items.handleObject
1625
+ * @see jQuery.Chain.services.items.handleElement
1626
+ * @see jQuery.Chain.services.items.handleArray
1627
+ * @see jQuery.Chain.services.items.handleNumber
1628
+ * @see jQuery.Chain.services.items.handleTrue
1629
+ * @see jQuery.Chain.services.items.handleDefault
1630
+ */
1631
+ handler: function(obj)
1632
+ {
1633
+ // Array
1634
+ if(obj instanceof Array)
1635
+ {return this.handleArray(obj);}
1636
+ // Inactive
1637
+ else if(!this.isActive)
1638
+ {return $().eq(-1);}
1639
+ // jQuery Object
1640
+ else if($.Chain.jobject(obj))
1641
+ {return this.handleElement(obj);}
1642
+ // Normal Object
1643
+ else if(typeof obj == 'object')
1644
+ {return this.handleObject(obj);}
1645
+ // Number
1646
+ else if(typeof obj == 'number')
1647
+ {return this.handleNumber(obj);}
1648
+ // True
1649
+ else if(obj === true)
1650
+ {return this.handleTrue();}
1651
+ // Default
1652
+ else
1653
+ {return this.handleDefault();}
1654
+ },
1655
+
1656
+ /**
1657
+ * If a Data Object is given, it will return the item element
1658
+ * containing the object if it exists, otherwise empty.
1659
+ *
1660
+ * @alias items(object)
1661
+ * @alias jQuery.Chain.services.items.handleObject
1662
+ *
1663
+ * @param {Object} obj Data Object
1664
+ *
1665
+ * @return {Object} jQuery Object
1666
+ */
1667
+ handleObject: function(obj)
1668
+ {
1669
+ // Get Element By Data
1670
+ return this.collection('all').filter(function(){return $(this).item() == obj;});
1671
+ },
1672
+
1673
+ /**
1674
+ * If a jQuery Element is given, it will return itself if it is part of the items,
1675
+ * otherwise empty jQuery object.
1676
+ *
1677
+ * @alias items(element)
1678
+ * @alias jQuery.Chain.services.items.handleElement
1679
+ *
1680
+ * @param {Object} obj jQuery Object
1681
+ *
1682
+ * @return {Object} jQuery Object
1683
+ */
1684
+ handleElement: function(obj)
1685
+ {
1686
+ // Check element whether it is part of items or not.
1687
+ if(!$.Chain.jidentic(obj, obj.item('root')) && $.Chain.jidentic(this.element, obj.item('root')))
1688
+ {return obj;}
1689
+ else
1690
+ {return $().eq(-1);}
1691
+ },
1692
+
1693
+ /**
1694
+ * If array is given, it will merge it to current items
1695
+ *
1696
+ * @alias items(array)
1697
+ * @alias jQuery.Chain.services.items.handleArray
1698
+ *
1699
+ * @param {Array} array Array of Data
1700
+ *
1701
+ * @return {Object} jQuery Object
1702
+ */
1703
+ handleArray: function(array)
1704
+ {
1705
+ // Array will be merged in
1706
+ return this.$merge(array);
1707
+ },
1708
+
1709
+ /**
1710
+ * If number is given, it will get the object with the current number. Use -1 to get the last number.
1711
+ *
1712
+ * @alias items(number)
1713
+ * @alias jQuery.Chain.services.items.handleNumber
1714
+ *
1715
+ * @param {Number} number Index
1716
+ *
1717
+ * @return {Object} jQuery Object
1718
+ */
1719
+ handleNumber: function(number)
1720
+ {
1721
+ // if -1, it will get the last.
1722
+ if(number == -1)
1723
+ {return this.collection('visible').filter(':last');}
1724
+ else
1725
+ {return this.collection('visible').eq(number);}
1726
+ },
1727
+
1728
+ /**
1729
+ * If @true@ is given, it will get all items including the hidden one.
1730
+ *
1731
+ * @alias items(true)
1732
+ * @alias jQuery.Chain.services.items.handleTrue
1733
+ *
1734
+ * @return {Object} jQuery Object
1735
+ *
1736
+ * @see jQuery.Chain.services.items.collections.all
1737
+ */
1738
+ handleTrue: function()
1739
+ {
1740
+ return this.collection('all');
1741
+ },
1742
+
1743
+ /**
1744
+ * If nothing is given, it will get all visible items.
1745
+ *
1746
+ * @alias items(true)
1747
+ * @alias jQuery.Chain.services.items.handleTrue
1748
+ *
1749
+ * @return {Object} jQuery Object
1750
+ *
1751
+ * @see jQuery.Chain.services.items.collections.visible
1752
+ */
1753
+ handleDefault: function()
1754
+ {
1755
+ return this.collection('visible');
1756
+ },
1757
+
1758
+ /**
1759
+ * Update element
1760
+ *
1761
+ * @alias jQuery.Chain.services.items.update
1762
+ */
1763
+ update: function()
1764
+ {
1765
+ this.element.update();
1766
+ },
1767
+
1768
+ /**
1769
+ * Clear all items
1770
+ *
1771
+ * @alias jQuery.Chain.services.items.empty
1772
+ */
1773
+ empty: function()
1774
+ {
1775
+ var all = this.collection('all');
1776
+
1777
+ // Remove items
1778
+ // Make it run in the background. for responsiveness.
1779
+ setTimeout(function(){all.each(function(){$(this).item('remove', true);});}, 1);
1780
+
1781
+ // Empty anchor container
1782
+ this.element.chain('anchor').empty();
1783
+ },
1784
+
1785
+ /**
1786
+ * Get collection of items. Define a collection by adding a function argument
1787
+ *
1788
+ * @alias jQuery.Chain.services.items.collection
1789
+ *
1790
+ * @param {String} col Collection name
1791
+ * @param {Function} fn Create a collection function
1792
+ *
1793
+ * @return {Object} jQuery Object
1794
+ */
1795
+ collection: function(col, fn)
1796
+ {
1797
+ if(arguments.length > 1)
1798
+ {
1799
+ if(typeof fn == 'function')
1800
+ {this.collections[col] = fn;}
1801
+
1802
+ return this.element;
1803
+ }
1804
+ else
1805
+ {
1806
+ if(this.collections[col])
1807
+ {return this.collections[col].apply(this);}
1808
+ else
1809
+ {return $().eq(-1);}
1810
+ }
1811
+
1812
+ },
1813
+
1814
+ /**
1815
+ * Items Updater, called by @$(element).update()@
1816
+ *
1817
+ * @alias items('update')
1818
+ * @alias jQuery.Chain.services.items.$update
1819
+ *
1820
+ * @return {Object} jQuery Element
1821
+ */
1822
+ $update: function()
1823
+ {
1824
+ if(!this.element.chain('active') || !this.isActive)
1825
+ {return this.element;}
1826
+
1827
+ var self = this;
1828
+ var builder = this.element.chain('builder');
1829
+ var template = this.element.chain('template');
1830
+ var push;
1831
+
1832
+ var iterator = function(){
1833
+ var clone = template
1834
+ .clone()[push ? 'appendTo' :'prependTo'](self.element.chain('anchor'))
1835
+ .addClass('chain-item')
1836
+ .item('root', self.element);
1837
+
1838
+ if(self.linkElement && $.Chain.jobject(this) && this.item())
1839
+ {clone.item('link', this, 'self');}
1840
+ else
1841
+ {clone.item(this);}
1842
+
1843
+ clone.chain(builder, true);
1844
+ };
1845
+
1846
+ push = false;
1847
+ $.each(this.shiftBuffer, iterator);
1848
+ push = true;
1849
+ $.each(this.pushBuffer, iterator);
1850
+
1851
+
1852
+ this.shiftBuffer = [];
1853
+ this.pushBuffer = [];
1854
+
1855
+ return this.element;
1856
+ },
1857
+
1858
+ /**
1859
+ * Add item(s). use @items('add', 'shift', item)@ to add item at the top
1860
+ *
1861
+ * @alias items('add')
1862
+ * @alias jQuery.Chain.services.items.$add
1863
+ *
1864
+ * @param {Object} item
1865
+ *
1866
+ * @return {Object} jQuery Object
1867
+ */
1868
+ $add: function()
1869
+ {
1870
+ if(this.linkElement)
1871
+ {return this.element;}
1872
+
1873
+ var cmd;
1874
+ var args = Array.prototype.slice.call(arguments);
1875
+ // Extract command
1876
+ if(typeof args[0] == 'string')
1877
+ {cmd = args.shift();}
1878
+
1879
+ var buffer = (cmd == 'shift') ? 'shiftBuffer' : 'pushBuffer';
1880
+
1881
+ this.isActive = true;
1882
+ this[buffer] = this[buffer].concat(args);
1883
+ this.update();
1884
+
1885
+ return this.element;
1886
+ },
1887
+
1888
+ /**
1889
+ * Merge items with array of item data
1890
+ *
1891
+ * @alias items('merge')
1892
+ * @alias jQuery.Chain.services.items.$merge
1893
+ *
1894
+ * @param {String} cmd Switch for push/shift
1895
+ * @param {Array} items Item Data
1896
+ *
1897
+ * @return {Object} jQuery Element
1898
+ */
1899
+ $merge: function(cmd, items)
1900
+ {
1901
+ if(this.linkElement)
1902
+ {return this.element;}
1903
+
1904
+ if(typeof cmd != 'string')
1905
+ {items = cmd;}
1906
+ var buffer = (cmd == 'shift') ? 'shiftBuffer' : 'pushBuffer';
1907
+
1908
+ this.isActive = true;
1909
+ if($.Chain.jobject(items))
1910
+ {this[buffer] = this[buffer].concat(items.map(function(){return $(this);}).get());}
1911
+ else if(items instanceof Array)
1912
+ {this[buffer] = this[buffer].concat(items);}
1913
+ this.update();
1914
+
1915
+ return this.element;
1916
+ },
1917
+
1918
+ /**
1919
+ * Replace items with new items array
1920
+ *
1921
+ * @alias items('replace')
1922
+ * @alias jQuery.Chain.services.items.$replace
1923
+ *
1924
+ * @param {String} cmd Switch for push/shift
1925
+ * @param {Array} items Item Data
1926
+ *
1927
+ * @return {Object} jQuery Element
1928
+ */
1929
+ $replace: function(cmd, items)
1930
+ {
1931
+ if(this.linkElement && arguments.callee.caller != this.linkUpdater)
1932
+ {return this.element;}
1933
+
1934
+ if(typeof cmd != 'string')
1935
+ {items = cmd;}
1936
+ var buffer = (cmd == 'shift') ? 'shiftBuffer' : 'pushBuffer';
1937
+
1938
+ this.isActive = true;
1939
+ this.empty();
1940
+
1941
+ if($.Chain.jobject(items))
1942
+ {this[buffer] = items.map(function(){return $(this);}).get();}
1943
+ else if(items instanceof Array)
1944
+ {this[buffer] = items;}
1945
+
1946
+ this.update();
1947
+
1948
+ return this.element;
1949
+ },
1950
+
1951
+ /**
1952
+ * Remove item
1953
+ *
1954
+ * @alias items('remove')
1955
+ * @alias jQuery.Chain.services.items.$remove
1956
+ *
1957
+ * @param {Object, Number} item
1958
+ *
1959
+ * @return {Object} jQuery Object
1960
+ */
1961
+ $remove: function()
1962
+ {
1963
+ if(this.linkElement)
1964
+ {return this.element;}
1965
+
1966
+ for(var i=0; i<arguments.length; i++)
1967
+ {this.handler(arguments[i]).item('remove', true);}
1968
+ this.update();
1969
+
1970
+ return this.element;
1971
+ },
1972
+
1973
+ /**
1974
+ * Reorder Item
1975
+ *
1976
+ * @alias items('reorder')
1977
+ * @alias jQuery.Chain.services.items.$reorder
1978
+ *
1979
+ * @param {Object} item1 Item 1
1980
+ * @param {Object} item2 Item 2
1981
+ *
1982
+ * @return {Object} jQuery object
1983
+ */
1984
+ $reorder: function(item1, item2)
1985
+ {
1986
+ if(item2)
1987
+ {this.handler(item1).before(this.handler(item2));}
1988
+ else
1989
+ {this.handler(item1).appendTo(this.element.chain('anchor'));}
1990
+ this.update();
1991
+
1992
+ return this.element;
1993
+ },
1994
+
1995
+ /**
1996
+ * Clear all items
1997
+ *
1998
+ * @alias items('empty')
1999
+ * @alias jQuery.Chain.services.items.$empty
2000
+ *
2001
+ * @return {Object} jQuery object
2002
+ */
2003
+ $empty: function()
2004
+ {
2005
+ if(this.linkElement)
2006
+ {return this.element;}
2007
+
2008
+ this.empty();
2009
+ this.shiftBuffer = [];
2010
+ this.pushBuffer = [];
2011
+ this.update();
2012
+
2013
+ return this.element;
2014
+ },
2015
+
2016
+ /**
2017
+ * Like @items()@ but returns array of data instead of the jQuery object.
2018
+ *
2019
+ * @alias items('data')
2020
+ * @alias jQuery.Chain.services.items.$data
2021
+ *
2022
+ * @return {Array} list of data
2023
+ */
2024
+ $data: function(x)
2025
+ {
2026
+ return this.handler(x).map(function(){return $(this).item();}).get();
2027
+ },
2028
+
2029
+ /**
2030
+ * Bind Items to other (chained) element. If one of them is updated,
2031
+ * the linked element will be updated.
2032
+ *
2033
+ * @alias items('link')
2034
+ * @alias jQuery.Chain.services.items.$link
2035
+ *
2036
+ * @param {Object} element element/selector to be linked with
2037
+ * @param {String} collection Collection to be linked with (has to be @"self"@ if linked to item)
2038
+ *
2039
+ * @return {Object} jQuery Element
2040
+ *
2041
+ * @see jQuery.Chain.services.items.collection
2042
+ */
2043
+ $link: function(element, collection)
2044
+ {
2045
+ // Remove linked element if it already exist
2046
+ if(this.linkElement)
2047
+ {
2048
+ this.linkElement.unbind('update', this.linkUpdater);
2049
+ this.linkElement = null;
2050
+ }
2051
+
2052
+ element = $(element);
2053
+ // If element exists
2054
+ if(element.length)
2055
+ {
2056
+ var self = this;
2057
+ this.linkElement = element;
2058
+ // Create Collector Function
2059
+ this.linkFunction = function()
2060
+ {
2061
+ if(typeof collection == 'function')
2062
+ {
2063
+ try{
2064
+ return collection.call(self.element, self.linkElement);
2065
+ }catch(e){
2066
+ return $().eq(-1);
2067
+ }
2068
+ }
2069
+ else if(typeof collection == 'string')
2070
+ {
2071
+ return self.linkElement.items('collection', collection);
2072
+ }
2073
+ else
2074
+ {
2075
+ return $().eq(-1);
2076
+ }
2077
+ };
2078
+
2079
+ // Create Updater Function
2080
+ this.linkUpdater = function()
2081
+ {
2082
+ self.$replace(self.linkFunction());
2083
+ };
2084
+
2085
+ // Bind updater to linked element
2086
+ this.linkElement.bind('update', this.linkUpdater);
2087
+ this.linkUpdater();
2088
+ }
2089
+
2090
+ return this.element;
2091
+ },
2092
+
2093
+ /**
2094
+ * Get index of an Item
2095
+ *
2096
+ * @alias items('index')
2097
+ * @alias jQuery.Chain.services.items.$index
2098
+ *
2099
+ * @param {Object} item
2100
+ *
2101
+ * @return {Number} index
2102
+ */
2103
+ $index: function(item)
2104
+ {
2105
+ return this.collection('all').index(this.handler(item));
2106
+ },
2107
+
2108
+ /**
2109
+ * Get collection of items. Define a collection by adding a function argument
2110
+ *
2111
+ * @alias items('collection')
2112
+ * @alias jQuery.Chain.services.items.$collection
2113
+ *
2114
+ * @param {String} col Collection name
2115
+ * @param {Function} fn Create a collection function
2116
+ *
2117
+ * @return {Object} jQuery Object
2118
+ */
2119
+ $collection: function()
2120
+ {
2121
+ return this.collection.apply(this, Array.prototype.slice.call(arguments));
2122
+ },
2123
+
2124
+ /**
2125
+ * Check Status of @items@
2126
+ *
2127
+ * @alias items('active')
2128
+ * @alias jQuery.Chain.services.items.$active
2129
+ *
2130
+ * @return {Boolean} Status
2131
+ */
2132
+ $active: function()
2133
+ {
2134
+ return this.isActive;
2135
+ },
2136
+
2137
+ /**
2138
+ * Backup Item to the state before being built.
2139
+ *
2140
+ * @alias items('backup')
2141
+ * @alias jQuery.Chain.services.items.$backup
2142
+ *
2143
+ * @return {Object} jQuery Object
2144
+ */
2145
+ $backup: function()
2146
+ {
2147
+ if(!this.element.chain('active') || !this.isActive)
2148
+ {return this.element;}
2149
+
2150
+ var buffer = [];
2151
+ this.collection('all').each(function(){
2152
+ var item = $(this).item();
2153
+ if(item)
2154
+ {buffer.push(item);}
2155
+ });
2156
+
2157
+ this.pushBuffer = buffer.concat(this.pushBuffer);
2158
+
2159
+ this.empty();
2160
+
2161
+ return this.element;
2162
+ },
2163
+
2164
+ /**
2165
+ * Destroy items service.
2166
+ *
2167
+ * @alias items('destroy')
2168
+ * @alias jQuery.Chain.services.items.$destroy
2169
+ *
2170
+ * @return {Object} jQuery Element
2171
+ */
2172
+ $destroy: function()
2173
+ {
2174
+ this.empty();
2175
+ return this.element;
2176
+ }
2177
+ });
2178
+
2179
+ // Filtering extension
2180
+ $.Chain.extend('items', {
2181
+ /**
2182
+ * Filtering subroutine
2183
+ *
2184
+ * @alias jQuery.Chain.services.items.doFilter
2185
+ */
2186
+ doFilter: function()
2187
+ {
2188
+ var props = this.searchProperties;
2189
+ var text = this.searchText;
2190
+
2191
+ if(text)
2192
+ {
2193
+ // Make text lowerCase if it is a string
2194
+ if(typeof text == 'string')
2195
+ {text = text.toLowerCase();}
2196
+
2197
+ // Filter items
2198
+ var items = this.element.items(true).filter(function(){
2199
+ var data = $(this).item();
2200
+ // If search properties is defined, search for text in those properties
2201
+ if(props)
2202
+ {
2203
+ for(var i=0; i<props.length; i++)
2204
+ {
2205
+ if(typeof data[props[i]] == 'string'
2206
+ && !!(typeof text == 'string' ? data[props[i]].toLowerCase() : data[props[i]]).match(text))
2207
+ {return true;}
2208
+ }
2209
+ }
2210
+ // Otherwise search in all properties
2211
+ else
2212
+ {
2213
+ for(var prop in data)
2214
+ {
2215
+ if(typeof data[prop] == 'string'
2216
+ && !!(typeof text == 'string' ? data[prop].toLowerCase() : data[prop]).match(text))
2217
+ {return true;}
2218
+ }
2219
+ }
2220
+ });
2221
+ this.element.items(true).not(items).hide();
2222
+ items.show();
2223
+ }
2224
+ else
2225
+ {
2226
+ this.element.items(true).show();
2227
+ this.element.unbind('preupdate', this.searchBinding);
2228
+ this.searchBinding = null;
2229
+ }
2230
+ },
2231
+
2232
+ /**
2233
+ * Filter items by criteria. Filtered items will be hidden.
2234
+ *
2235
+ * @alias items('filter')
2236
+ * @alias jQuery.Chain.services.items.$filter
2237
+ *
2238
+ * @param {String, RegExp} text Search keyword
2239
+ * @param {String, Array} properties Search properties
2240
+ *
2241
+ * @return {Object} jQuery Object
2242
+ */
2243
+ $filter: function(text, properties)
2244
+ {
2245
+ // If no argument, just refilter
2246
+ if(!arguments.length)
2247
+ {return this.update();}
2248
+
2249
+ this.searchText = text;
2250
+
2251
+ if(typeof properties == 'string')
2252
+ {this.searchProperties = [properties];}
2253
+ else if(properties instanceof Array)
2254
+ {this.searchProperties = properties;}
2255
+ else
2256
+ {this.searchProperties = null;}
2257
+
2258
+ // Bind to preupdate
2259
+ if(!this.searchBinding)
2260
+ {
2261
+ var self = this;
2262
+ this.searchBinding = function(event, item){self.doFilter();};
2263
+ this.element.bind('preupdate', this.searchBinding);
2264
+ }
2265
+
2266
+ return this.update();
2267
+ }
2268
+ });
2269
+
2270
+ // Sorting extension
2271
+ $.Chain.extend('items', {
2272
+ /**
2273
+ * Sorting subroutine
2274
+ *
2275
+ * @alias jQuery.Chain.services.items.doSort
2276
+ */
2277
+ doSort: function()
2278
+ {
2279
+ var name = this.sortName;
2280
+ var opt = this.sortOpt;
2281
+
2282
+ var sorter =
2283
+ {
2284
+ 'number': function(a, b){
2285
+ return parseFloat(($(a).item()[name]+'').match(/\d+/gi)[0])
2286
+ - parseFloat(($(b).item()[name]+'').match(/\d+/gi)[0]);
2287
+ },
2288
+
2289
+ 'default': function(a, b){
2290
+ return $(a).item()[name] > $(b).item()[name] ? 1 : -1;
2291
+ }
2292
+ };
2293
+
2294
+ if(name)
2295
+ {
2296
+ var sortfn = opt.fn || sorter[opt.type] || sorter['default'];
2297
+
2298
+ var array = this.element.items(true).get().sort(sortfn);
2299
+
2300
+ array = opt.desc ? array.reverse() : array;
2301
+
2302
+ for(var i=0; i<array.length; i++)
2303
+ {this.element.chain('anchor').append(array[i]);}
2304
+
2305
+ opt.desc = opt.toggle ? !opt.desc : opt.desc;
2306
+ }
2307
+ else
2308
+ {
2309
+ this.element.unbind('preupdate', this.sortBinding);
2310
+ this.sortBinding = null;
2311
+ }
2312
+ },
2313
+
2314
+ /**
2315
+ * Sort items by property.
2316
+ *
2317
+ * @alias items('sort')
2318
+ * @alias jQuery.Chain.services.items.$sort
2319
+ *
2320
+ * @param {String} name sorting property
2321
+ * @param {Object} opt {toggle:true/false, desc:true/false, type:'number/default'}
2322
+ *
2323
+ * @return {Object} jQuery Object
2324
+ */
2325
+ $sort: function(name, opt)
2326
+ {
2327
+ if(!name && name !== null && name !== false)
2328
+ {return this.update();}
2329
+
2330
+ if(this.sortName != name)
2331
+ {this.sortOpt = $.extend({desc:false, type:'default', toggle:false}, opt);}
2332
+ else
2333
+ {$.extend(this.sortOpt, opt);}
2334
+
2335
+ this.sortName = name;
2336
+
2337
+ if(!this.sortBinding)
2338
+ {
2339
+ var self = this;
2340
+ this.sortBinding = function(event, item){self.doSort();};
2341
+ this.element.bind('preupdate', this.sortBinding);
2342
+ }
2343
+
2344
+ return this.update();
2345
+ }
2346
+ });
2347
+
2348
+ })(jQuery);