bowline 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +1 -0
  2. data/History.txt +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/Manifest.txt +58 -0
  5. data/README.txt +136 -0
  6. data/Rakefile +25 -0
  7. data/assets/jquery.bowline.js +96 -0
  8. data/assets/jquery.chain.js +2348 -0
  9. data/assets/jquery.js +3549 -0
  10. data/bin/bowline-gen +6 -0
  11. data/bowline.gemspec +45 -0
  12. data/examples/account.rb +31 -0
  13. data/examples/example.js +24 -0
  14. data/examples/tweets.rb +28 -0
  15. data/examples/twitter.html +39 -0
  16. data/examples/users.rb +37 -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 +11 -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 +58 -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 +90 -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 +13 -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 +25 -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 +18 -0
  58. data/templates/script/run +3 -0
  59. metadata +155 -0
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg
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
+ .gitignore
2
+ History.txt
3
+ MIT-LICENSE
4
+ Manifest.txt
5
+ README.txt
6
+ Rakefile
7
+ assets/jquery.bowline.js
8
+ assets/jquery.chain.js
9
+ assets/jquery.js
10
+ bin/bowline-gen
11
+ bowline.gemspec
12
+ examples/account.rb
13
+ examples/example.js
14
+ examples/twitter.html
15
+ examples/tweets.rb
16
+ examples/users.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
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,136 @@
1
+ = Bowline
2
+
3
+ http://github.com/maccman/bowline
4
+
5
+ = DESCRIPTION
6
+
7
+ Ruby desktop application framework
8
+
9
+ = FEATURES
10
+
11
+ * MVC
12
+ * Uses Webkit
13
+ * View in HTML/JavaScript
14
+ * Binding between HTML & Ruby
15
+ * Will be cross platform (though only OSX atm)
16
+
17
+ = CONTACT
18
+
19
+ info@eribium.org
20
+ http://eribium.org
21
+ http://twitter.com/maccman
22
+
23
+ = Usage - Building a basic Twitter client
24
+
25
+ Build Titanium by following the instructions here:
26
+ http://wiki.github.com/marshall/titanium/build-instructions
27
+
28
+ Install the gem:
29
+ >> sudo gem install maccman-bowline --source http://gems.github.com
30
+
31
+ Run the app/binder generators:
32
+ >> bowline-gen app bowline_twitter
33
+ >> cd bowline_twitter
34
+ >> bowline-gen binder tweets
35
+
36
+ Copy tweets.rb from examples to app/binders/tweets.rb
37
+ Add your Twitter credentials to tweets.rb - in this simple example they're not dynamic.
38
+
39
+ Copy twitter.html from examples to public/index.html
40
+
41
+ Install the Twitter gem:
42
+ >> sudo gem install twitter
43
+
44
+ Add the Twitter gem to config/environment.rb:
45
+ config.gem "twitter"
46
+
47
+ run:
48
+ >> rake app:bundle TIPATH=~/path/to/titanium
49
+
50
+ run:
51
+ ./script/run
52
+
53
+ That's it
54
+
55
+ = EXAMPLES
56
+
57
+ Usage for a collection (of users):
58
+
59
+ module Binders
60
+ class Users < Bowline::Collection
61
+ # These are class methods
62
+ # i.e. methods that appear on
63
+ # users, rather an user
64
+ class << self
65
+ def index
66
+ # self.items is a magic variable -
67
+ # it'll update the html binders
68
+ self.items = User.all
69
+ end
70
+
71
+ def admins
72
+ # This just replaces all the listed
73
+ # users with just admins
74
+ self.items = User.admins.all
75
+ end
76
+ end
77
+
78
+ # Singleton methods, get added
79
+ # to individual users.
80
+ #
81
+ # self.element is the jQuery element
82
+ # for that user, so calling highlight
83
+ # on it is equivalent to:
84
+ # $(user).highlight()
85
+ #
86
+ # self.item is the user object, in this case
87
+ # an ActiveRecord instance
88
+ #
89
+ # self.page gives you access to the dom, e.g:
90
+ # self.page.alert('hello world')
91
+
92
+ def destroy
93
+ self.item.destroy
94
+ self.element.remove
95
+ end
96
+ end
97
+ end
98
+
99
+ <html>
100
+ <head>
101
+ <script src="jquery.js" type="text/javascript" charset="utf-8"></script>
102
+ <script src="chain.js" type="text/javascript" charset="utf-8"></script>
103
+ <script src="bowline.js" type="text/javascript" charset="utf-8"></script>
104
+ <script type="text/javascript" charset="utf-8">
105
+ jQuery(function($){
106
+ // Bind the element users to UserBinder
107
+ var users = $('#users').bowline('users', function(){
108
+ var self = $(this);
109
+ self.find('.destroy').click(function(){
110
+ self.invoke('destroy');
111
+ return false;
112
+ })
113
+ });
114
+
115
+ $('#showAdmins').click(function(){
116
+ users.invoke('admins');
117
+ return false;
118
+ });
119
+
120
+ // Populate with all the users
121
+ users.invoke('index');
122
+ });
123
+ </script>
124
+ </head>
125
+ <body>
126
+ <div id="users">
127
+ <div class="item">
128
+ <span class="name"></span>
129
+ <span class="email"></span>
130
+ <a href="#" class="destroy">Delete</a>
131
+ </div>
132
+ </div>
133
+
134
+ <a href="#" id="showAdmins">Show admins</a>
135
+ </body>
136
+ </html>
data/Rakefile ADDED
@@ -0,0 +1,25 @@
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_deps << ['activesupport', '>=2.3.2']
12
+ p.extra_dev_deps = [
13
+ ['newgem', ">= #{::Newgem::VERSION}"]
14
+ ]
15
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
16
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
17
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
18
+ p.rsync_args = '-av --delete --ignore-errors'
19
+ end
20
+
21
+ require 'newgem/tasks' # load /tasks/*.rake
22
+ Dir['tasks/**/*.rake'].each { |t| load t }
23
+
24
+ # TODO - want other tests/tasks run by default? Add them to the list
25
+ # 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);