├── .gitignore ├── .npmignore ├── History.md ├── LICENSE ├── README.md ├── examples ├── guide │ ├── app.js │ ├── bootstrap.min.css │ ├── github.min.css │ ├── highlight.min.js │ ├── index.html │ └── style.css ├── todos │ ├── base.css │ ├── bg.png │ ├── css │ │ ├── app.css │ │ ├── base.css │ │ └── bg.png │ ├── index.html │ ├── js │ │ └── app.js │ └── lib │ │ ├── app.js │ │ ├── jquery.min.js │ │ ├── o_O.js │ │ └── o_O.router.js └── zoom │ ├── index.html │ ├── jquery.drag.js │ └── zoom.js ├── ideas ├── o_O.list.js └── o_O.memory_store.js ├── o_O.collection.js ├── o_O.js ├── o_O.router.js ├── package.json ├── test ├── alt_namespace.html ├── lib │ ├── expect.js │ ├── jquery.js │ ├── mocha.css │ └── mocha.js ├── mocha-dom.html ├── mocha.html ├── o_O.test.css ├── test.array.dom.js ├── test.array.js ├── test.binding.dom.js ├── test.binding.js ├── test.conditionals.js ├── test.eventize.js ├── test.expressions.js ├── test.model.js ├── test.property.js └── test.timeout.js.old └── test_router ├── test.router.js └── test_router.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | o_O-*.tgz 4 | .DS_Store 5 | o_O.min.js 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples/** 2 | test/** 3 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 0.2.7 2 | ===== 3 | 4 | * fix for property.mirror 5 | * 'with' binding now ignores it's own element (as it should) 6 | * added model::each which enumerates the properties 7 | * changes to renderItem for o_O.array - since an item might belong to > 1 bound lists 8 | * onbind is back - these bindings occur after all other bindings have run 9 | 10 | 0.2.6 11 | ===== 12 | 13 | * model class itself is now evented 14 | * array can now be extended 15 | * added o_O.collection example file 16 | * fixed bad enumeration in array::find 17 | * extended models dont need type on creation 18 | 19 | 0.2.5 20 | ===== 21 | 22 | * refactor bindings. Can now omit parans mostly - are added automatically. 23 | * bindings are now explicit, and o_O now knows what type of binding each is (inbound/outbound/twoway) 24 | * add new bindings with o_O.newBinding 25 | * performance improvements 26 | * added router 27 | 28 | 29 | 0.2.4 30 | ===== 31 | 32 | * force read on emit 33 | * added dependencies as a property 34 | 35 | 0.2.3 36 | ===== 37 | 38 | * properties now emit synchronously 39 | * binding now emit asynchronously via requestAnimationFrame (for HTML writing events), except for the first run 40 | * Added zooming example 41 | 42 | 0.2.2 43 | ===== 44 | 45 | * Moved version string 46 | * Added ability to export to an alternative namespace 47 | * extended support for bind to o_O.model and o_O.array 48 | 49 | 0.2.1 50 | ===== 51 | 52 | * Set array length before emitting events 53 | * Rewrite array::renderItem 54 | * Added property.bind 55 | * Computed properties now also emit 56 | 57 | 0.2.0 58 | ===== 59 | 60 | * o_O.array 61 | * o_O.model 62 | * Timeouts per property -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012 Jonah Fox (https://github.com/weepy) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```funnyface 2 | ,ad8888ba, 3 | d8"' `"8b 4 | d8' `8b 5 | ,adPPYba, 88 88 6 | a8" "8a 88 88 7 | 8b d8 Y8, ,8P 8 | "8a, ,a8" Y8a. .a8P 9 | `"YbbdP"' `"Y8888Y"' 10 | 11 | 888888888888 12 | ``` 13 | 14 | # Funnyface.js 15 | ## HTML binding for teh lulz 16 | 17 | * Elegantly binds objects to HTML 18 | * Proxies through jQuery, Ender, or whatever $ is 19 | * Automatic dependency resolution 20 | * Plays well with others 21 | 22 | 23 | ## Examples 24 | 25 | * [Basic Overview](http://weepy.github.com/o_O/examples/guide/index.html) 26 | * [TodoMVC Example](http://weepy.github.com/o_O/examples/todos/index.html) 27 | 28 | ## The Basics 29 | 30 | # o_O properties 31 | 32 | Use `o_O(...)` to create an evented o_O `property`: 33 | 34 | ```javascript 35 | 36 | name = o_O('Homer Simpson') 37 | 38 | // read a value 39 | name() // => 'Homer Simpson' 40 | 41 | // write a value 42 | name('Bart Simpson') 43 | ``` 44 | 45 | o_O properties are evented, so it's possible to bind to a change event: 46 | 47 | ``` 48 | name.change(function(new_name, old_name) { 49 | console.log('my name changed from', old_name, 'to', new_name) 50 | }) 51 | ``` 52 | 53 | # Computed properties 54 | 55 | o_O can also create computed properties: 56 | 57 | ```javascript 58 | firstName = o_O('Homer') 59 | surName = o_O('Simpson') 60 | fullName = function() { 61 | return firstName() + ' ' + surName() 62 | } 63 | 64 | fullName() // => 'Homer Simpson' 65 | ``` 66 | 67 | A computed property automatically determines it's dependencies and is recalculated whenever a dependency changes: 68 | 69 | ```javascript 70 | firstName('Bart') 71 | 72 | fullName() // => 'Bart Simpson' 73 | ``` 74 | 75 | # HTML binding with o_O.bind 76 | 77 | Bind an object to a section of HTML with the `o_O.bind(...)` method, and bind parts of that HTML section to o_O `properties` with the `data-bind` attribute: 78 | 79 | ```javascript 80 | person = { 81 | firstName: o_O('Michael'), 82 | surName: o_O('Jackson'), 83 | fullName: function() { 84 | return person.firstName() + ' ' + person.surName() 85 | }, 86 | age: o_O(50) 87 | } 88 | o_O.bind(person, '#person'); 89 | ``` 90 | ```html 91 |
92 |
93 |
94 |
95 | ``` 96 | 97 | This will render the HTML and retrigger the bindings whenever a dependency changes. So e.g calling `person.firstName('Miss')` will update the HTML. 98 | 99 | The binding names are associated with jQuery (or whatever $ is), so css will call $.fn.css. There are also some custom bindings: 100 | 101 | * `foreach` : renders the innerHTML for a list of items 102 | * `value` : two-way binding for forms 103 | * `visible` : hides an element if falsey 104 | * `if/unless` : removes/shows the inner HTML 105 | * `with` : rebinds the context (similar to javascript `with`) 106 | * `options`: options for a select 107 | * `log`: outputs to console.log 108 | * `onbind`: general purpose 109 | 110 | Event handlers will also work, e.g. `click: handleClick`. 111 | 112 | NB if there's no corresponding binding found, it will simply update the attribute on the element; this is especially useful for attributes such as id, class, src, href 113 | 114 | ## Digging Deeper 115 | 116 | Besides creating basic javascript objects containing o_O `properties`, you can also create an o_O `model` (using `o_O.model(...)`) that creates o_O `properties` for you out of the box as well as giving you access to event aggregation: 117 | 118 | ```javascript 119 | var homer = o_O.model({ 120 | name: 'Homer Simpson', 121 | age: 40 122 | }); 123 | 124 | homer.on('set:name', function(character, name_new, name_old){ 125 | console.log("Homer's name changed."); 126 | }); 127 | ``` 128 | 129 | You can also create an o_O evented `array` that lets you create an array of items (can be anything) and if the items support it (i.e. they are o_O `models`) aggregates events across all of them: 130 | 131 | ```javascript 132 | var cast = o_O.array(); 133 | 134 | cast.push(o_O.model({name: 'Homer', age: 40})); 135 | cast.push(o_O.model({name: 'Marge', age: 36})); 136 | cast.push(o_O.model({name: 'Bart', age: 10})); 137 | cast.push(o_O.model({name: 'Lisa', age: 8})); 138 | cast.push(o_O.model({name: 'Maggie', age: 2})); 139 | 140 | cast.on('set:age', function(character, age_new, age_old){ 141 | console.log(character.name + "'s age changed from " + age_old + " to " + age_new + "."); 142 | }); 143 | 144 | // this will trigger the above 'set:' event for each character: 145 | cast.forEach(function(character){ 146 | character.age(character.age() + 1); 147 | }); 148 | 149 | cast.on('add', function(new_character){ 150 | console.log(new_character); 151 | }); 152 | 153 | // this will trigger the above 'add' event: 154 | cast.push(new Character({name: 'Mr. Burns', age: 99})); 155 | ``` 156 | 157 | The special `foreach` binding will render this list: 158 | 159 | ```html 160 | 163 | 164 | 167 | ``` 168 | 169 | ## Running Tests 170 | 171 | Make sure you have installed o_O's development dependencies via npm: 172 | 173 | ```bash 174 | npm install 175 | ``` 176 | 177 | A subset of tests can be run via the console: 178 | 179 | ```bash 180 | npm test 181 | ``` 182 | 183 | Or, if you have mocha install (`npm install -g mocha`) you can just run: 184 | 185 | ```bash 186 | mocha 187 | ``` 188 | 189 | Other tests (that rely on the browser's DOM) must be run in the browser: 190 | 191 | ```bash 192 | open test/mocha.html 193 | ``` 194 | 195 | ## Browser Compatability 196 | 197 | Tested in: 198 | 199 | * Chrome 16-18 200 | * Firefox 4-10 201 | * Internet Explorer 7-9 202 | * Safari 5 203 | * Node 6.0 204 | 205 | Other browsers should work, (eg IE6) but are currently untested 206 | 207 | ## Importing to alternate namespace 208 | 209 | It's possible to import o_O to an alternative namespace by appending ?mynamespace to the script, for example 210 | 211 | ```html 212 | 213 | ``` 214 | 215 | will import the library to `window` as `oO` 216 | 217 | 218 | ## Contributers 219 | 220 | * Jonah Fox aka weepy 221 | * Troy Goode 222 | 223 | ## License 224 | 225 | o_O is released under the [MIT license](http://www.opensource.org/licenses/mit-license.html): 226 | 227 | ``` 228 | The MIT License (MIT) 229 | 230 | Copyright (c) 2012 Jonah Fox (https://github.com/weepy) 231 | 232 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 233 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 234 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 235 | permit persons to whom the Software is furnished to do so, subject to the following conditions: 236 | 237 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 238 | Software. 239 | 240 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 241 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 242 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 243 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 244 | ``` 245 | -------------------------------------------------------------------------------- /examples/guide/app.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | 3 | $('a.btn').addClass('btn-large').click(function() { 4 | 5 | $('.btn-primary').removeClass('btn-primary') 6 | $(this).addClass('btn-primary') 7 | 8 | var ex = $(this).next() 9 | 10 | var out = $("
") 11 | 12 | $("#current").html("") 13 | 14 | var js = $.trim($(ex).find("script").html()) 15 | var code = $("", {html: js + ""}) 16 | out.prepend($("
").append(code))
17 |   
18 |   var orightml = $(ex).find('.inner').html()
19 |   
20 |   var html = $.trim(orightml)
21 |   
22 |   //html = "
\n " + html + "\n
" 23 | html = html.replace(/\").append($("", {html: html}))) 26 | 27 | out.append("
" + orightml + "
") 28 | 29 | //out.append(" 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

o_O

18 | 19 |
20 | 21 |
22 | Basic Binding 23 |
24 |
25 |

26 |
27 | 30 |
31 | 32 | 33 | 34 | Auto updating 35 |
36 |
37 |

38 | ms since 1st Jan 1970: 39 | 40 |

41 |
42 | 52 |
53 | 54 | 55 | 56 | 57 | Binding to text fields 58 | 59 |
60 |
61 | 62 | 63 |

64 |
65 | 70 |
71 | 72 | 73 | 74 | Checkbox 75 |
76 |
77 | 78 |

79 |
80 | 84 |
85 | 86 | 87 | 88 | Select 89 |
90 |
91 | 93 | 95 |

96 |
97 | 104 |
105 | 106 | 107 | 108 | 109 | Complex dependencies 110 |
111 |
112 |

113 | 114 | 115 |
116 | 126 |
127 | 128 | 129 | Lists 130 |
131 |
132 |
    133 |
  • 134 |
135 | 140 |
141 | 142 | 143 | Events and helpers 144 |
145 |
146 | 147 | 148 | 149 |
150 | 160 |
161 | 162 | 163 | visible, and css 164 |
165 |
166 | 167 | 168 | 169 | 170 |
171 | 178 |
179 | 180 | 181 | 182 | 183 | classes 184 |
185 | 186 |
187 | 188 | 189 |
190 | 199 |
200 | 201 | 202 | Animation 203 |
204 |
205 |

Animate Me!

206 | 207 | 208 |
209 | 227 |
228 | 229 | 241 | 242 | Rate Limiting 243 |
244 |
245 |

246 | 247 |
248 | Increments upon every mousemove 249 |
250 |
251 |
252 | Increments at most 150ms 253 |
254 |
255 | 256 | 280 |
281 | 282 | 283 | 284 |
285 |
286 | 287 |
288 | 289 |
290 |







291 | 292 | 293 |
294 | 295 | 296 | 299 | 300 | 301 | -------------------------------------------------------------------------------- /examples/guide/style.css: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |

Dragging

10 |

Drag the rectangles and also the background. Up/down is zoom

11 | 12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/zoom/jquery.drag.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.event.drag - v 2.0.0 3 | * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com 4 | * Open Source MIT License - http://threedubmedia.com/code/license 5 | */ 6 | // Created: 2008-06-04 7 | // Updated: 2010-06-07 8 | // REQUIRES: jquery 1.4.2+ 9 | 10 | ;(function( $ ){ 11 | 12 | // add the jquery instance method 13 | $.fn.drag = function( str, arg, opts ){ 14 | // figure out the event type 15 | var type = typeof str == "string" ? str : "", 16 | // figure out the event handler... 17 | fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null; 18 | // fix the event type 19 | if ( type.indexOf("drag") !== 0 ) 20 | type = "drag"+ type; 21 | // were options passed 22 | opts = ( str == fn ? arg : opts ) || {}; 23 | // trigger or bind event handler 24 | return fn ? this.bind( type, opts, fn ) : this.trigger( type ); 25 | }; 26 | 27 | // local refs (increase compression) 28 | var $event = $.event, 29 | $special = $event.special, 30 | // configure the drag special event 31 | drag = $special.drag = { 32 | 33 | // these are the default settings 34 | defaults: { 35 | which: 1, // mouse button pressed to start drag sequence 36 | distance: 0, // distance dragged before dragstart 37 | not: ':input', // selector to suppress dragging on target elements 38 | handle: null, // selector to match handle target elements 39 | relative: false, // true to use "position", false to use "offset" 40 | drop: true, // false to suppress drop events, true or selector to allow 41 | click: false // false to suppress click events after dragend (no proxy) 42 | }, 43 | 44 | // the key name for stored drag data 45 | datakey: "dragdata", 46 | 47 | // the namespace for internal live events 48 | livekey: "livedrag", 49 | 50 | // count bound related events 51 | add: function( obj ){ 52 | // read the interaction data 53 | var data = $.data( this, drag.datakey ), 54 | // read any passed options 55 | opts = obj.data || {}; 56 | // count another realted event 57 | data.related += 1; 58 | // bind the live "draginit" delegator 59 | if ( !data.live && obj.selector ){ 60 | data.live = true; 61 | $event.add( this, "draginit."+ drag.livekey, drag.delegate ); 62 | } 63 | // extend data options bound with this event 64 | // don't iterate "opts" in case it is a node 65 | $.each( drag.defaults, function( key, def ){ 66 | if ( opts[ key ] !== undefined ) 67 | data[ key ] = opts[ key ]; 68 | }); 69 | }, 70 | 71 | // forget unbound related events 72 | remove: function(){ 73 | $.data( this, drag.datakey ).related -= 1; 74 | }, 75 | 76 | // configure interaction, capture settings 77 | setup: function(){ 78 | // check for related events 79 | if ( $.data( this, drag.datakey ) ) 80 | return; 81 | // initialize the drag data with copied defaults 82 | var data = $.extend({ related:0 }, drag.defaults ); 83 | // store the interaction data 84 | $.data( this, drag.datakey, data ); 85 | // bind the mousedown event, which starts drag interactions 86 | $event.add( this, "mousedown", drag.init, data ); 87 | // prevent image dragging in IE... 88 | if ( this.attachEvent ) 89 | this.attachEvent("ondragstart", drag.dontstart ); 90 | }, 91 | 92 | // destroy configured interaction 93 | teardown: function(){ 94 | // check for related events 95 | if ( $.data( this, drag.datakey ).related ) 96 | return; 97 | // remove the stored data 98 | $.removeData( this, drag.datakey ); 99 | // remove the mousedown event 100 | $event.remove( this, "mousedown", drag.init ); 101 | // remove the "live" delegation 102 | $event.remove( this, "draginit", drag.delegate ); 103 | // enable text selection 104 | drag.textselect( true ); 105 | // un-prevent image dragging in IE... 106 | if ( this.detachEvent ) 107 | this.detachEvent("ondragstart", drag.dontstart ); 108 | }, 109 | 110 | // initialize the interaction 111 | init: function( event ){ 112 | // the drag/drop interaction data 113 | var dd = event.data, results; 114 | // check the which directive 115 | if ( dd.which > 0 && event.which != dd.which ) 116 | return; 117 | // check for suppressed selector 118 | if ( $( event.target ).is( dd.not ) ) 119 | return; 120 | // check for handle selector 121 | if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length ) 122 | return; 123 | // store/reset some initial attributes 124 | dd.propagates = 1; 125 | dd.interactions = [ drag.interaction( this, dd ) ]; 126 | dd.target = event.target; 127 | dd.pageX = event.pageX; 128 | dd.pageY = event.pageY; 129 | dd.dragging = null; 130 | // handle draginit event... 131 | results = drag.hijack( event, "draginit", dd ); 132 | // early cancel 133 | if ( !dd.propagates ) 134 | return; 135 | // flatten the result set 136 | results = drag.flatten( results ); 137 | // insert new interaction elements 138 | if ( results && results.length ){ 139 | dd.interactions = []; 140 | $.each( results, function(){ 141 | dd.interactions.push( drag.interaction( this, dd ) ); 142 | }); 143 | } 144 | // remember how many interactions are propagating 145 | dd.propagates = dd.interactions.length; 146 | // locate and init the drop targets 147 | if ( dd.drop !== false && $special.drop ) 148 | $special.drop.handler( event, dd ); 149 | // disable text selection 150 | drag.textselect( false ); 151 | // bind additional events... 152 | $event.add( document, "mousemove mouseup", drag.handler, dd ); 153 | // helps prevent text selection 154 | return false; 155 | }, 156 | // returns an interaction object 157 | interaction: function( elem, dd ){ 158 | return { 159 | drag: elem, 160 | callback: new drag.callback(), 161 | droppable: [], 162 | offset: $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left:0 } 163 | }; 164 | }, 165 | // handle drag-releatd DOM events 166 | handler: function( event ){ 167 | // read the data before hijacking anything 168 | var dd = event.data; 169 | // handle various events 170 | switch ( event.type ){ 171 | // mousemove, check distance, start dragging 172 | case !dd.dragging && 'mousemove': 173 | // drag tolerance, x² + y² = distance² 174 | if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) ) 175 | break; // distance tolerance not reached 176 | event.target = dd.target; // force target from "mousedown" event (fix distance issue) 177 | drag.hijack( event, "dragstart", dd ); // trigger "dragstart" 178 | if ( dd.propagates ) // "dragstart" not rejected 179 | dd.dragging = true; // activate interaction 180 | // mousemove, dragging 181 | case 'mousemove': 182 | if ( dd.dragging ){ 183 | // trigger "drag" 184 | drag.hijack( event, "drag", dd ); 185 | if ( dd.propagates ){ 186 | // manage drop events 187 | if ( dd.drop !== false && $special.drop ) 188 | $special.drop.handler( event, dd ); // "dropstart", "dropend" 189 | break; // "drag" not rejected, stop 190 | } 191 | event.type = "mouseup"; // helps "drop" handler behave 192 | } 193 | // mouseup, stop dragging 194 | case 'mouseup': 195 | $event.remove( document, "mousemove mouseup", drag.handler ); // remove page events 196 | if ( dd.dragging ){ 197 | if ( dd.drop !== false && $special.drop ) 198 | $special.drop.handler( event, dd ); // "drop" 199 | drag.hijack( event, "dragend", dd ); // trigger "dragend" 200 | } 201 | drag.textselect( true ); // enable text selection 202 | 203 | // if suppressing click events... 204 | if ( dd.click === false && dd.dragging ){ 205 | jQuery.event.triggered = 'click'; 206 | setTimeout(function(){ 207 | jQuery.event.triggered = undefined; 208 | }, 20 ); 209 | dd.dragging = false; // deactivate element 210 | } 211 | break; 212 | } 213 | }, 214 | 215 | // identify potential delegate elements 216 | delegate: function( event ){ 217 | // local refs 218 | var elems = [], target, 219 | // element event structure 220 | events = $.data( this, "events" ) || {}; 221 | // query live events 222 | $.each( events.live || [], function( i, obj ){ 223 | // no event type matches 224 | if ( obj.preType.indexOf("drag") !== 0 ) 225 | return; 226 | // locate the element to delegate 227 | target = $( event.target ).closest( obj.selector, event.currentTarget )[0]; 228 | // no element found 229 | if ( !target ) 230 | return; 231 | // add an event handler 232 | $event.add( target, obj.origType+'.'+drag.livekey, obj.origHandler, obj.data ); 233 | // remember new elements 234 | if ( $.inArray( target, elems ) < 0 ) 235 | elems.push( target ); 236 | }); 237 | // if there are no elements, break 238 | if ( !elems.length ) 239 | return false; 240 | // return the matched results, and clenup when complete 241 | return $( elems ).bind("dragend."+ drag.livekey, function(){ 242 | $event.remove( this, "."+ drag.livekey ); // cleanup delegation 243 | }); 244 | }, 245 | 246 | // re-use event object for custom events 247 | hijack: function( event, type, dd, x, elem ){ 248 | // not configured 249 | if ( !dd ) 250 | return; 251 | // remember the original event and type 252 | var orig = { event:event.originalEvent, type: event.type }, 253 | // is the event drag related or drog related? 254 | mode = type.indexOf("drop") ? "drag" : "drop", 255 | // iteration vars 256 | result, i = x || 0, ia, $elems, callback, 257 | len = !isNaN( x ) ? x : dd.interactions.length; 258 | // modify the event type 259 | event.type = type; 260 | // remove the original event 261 | event.originalEvent = null; 262 | // initialize the results 263 | dd.results = []; 264 | // handle each interacted element 265 | do if ( ia = dd.interactions[ i ] ){ 266 | // validate the interaction 267 | if ( type !== "dragend" && ia.cancelled ) 268 | continue; 269 | // set the dragdrop properties on the event object 270 | callback = drag.properties( event, dd, ia ); 271 | // prepare for more results 272 | ia.results = []; 273 | // handle each element 274 | $( elem || ia[ mode ] || dd.droppable ).each(function( p, subject ){ 275 | // identify drag or drop targets individually 276 | callback.target = subject; 277 | // handle the event 278 | result = subject ? $event.handle.call( subject, event, callback ) : null; 279 | // stop the drag interaction for this element 280 | if ( result === false ){ 281 | if ( mode == "drag" ){ 282 | ia.cancelled = true; 283 | dd.propagates -= 1; 284 | } 285 | if ( type == "drop" ){ 286 | ia[ mode ][p] = null; 287 | } 288 | } 289 | // assign any dropinit elements 290 | else if ( type == "dropinit" ) 291 | ia.droppable.push( drag.element( result ) || subject ); 292 | // accept a returned proxy element 293 | if ( type == "dragstart" ) 294 | ia.proxy = $( drag.element( result ) || ia.drag )[0]; 295 | // remember this result 296 | ia.results.push( result ); 297 | // forget the event result, for recycling 298 | delete event.result; 299 | // break on cancelled handler 300 | if ( type !== "dropinit" ) 301 | return result; 302 | }); 303 | // flatten the results 304 | dd.results[ i ] = drag.flatten( ia.results ); 305 | // accept a set of valid drop targets 306 | if ( type == "dropinit" ) 307 | ia.droppable = drag.flatten( ia.droppable ); 308 | // locate drop targets 309 | if ( type == "dragstart" && !ia.cancelled ) 310 | callback.update(); 311 | } 312 | while ( ++i < len ) 313 | // restore the original event & type 314 | event.type = orig.type; 315 | event.originalEvent = orig.event; 316 | // return all handler results 317 | return drag.flatten( dd.results ); 318 | }, 319 | 320 | // extend the callback object with drag/drop properties... 321 | properties: function( event, dd, ia ){ 322 | var obj = ia.callback; 323 | // elements 324 | obj.drag = ia.drag; 325 | obj.proxy = ia.proxy || ia.drag; 326 | // starting mouse position 327 | obj.startX = dd.pageX; 328 | obj.startY = dd.pageY; 329 | // current distance dragged 330 | obj.deltaX = event.pageX - dd.pageX; 331 | obj.deltaY = event.pageY - dd.pageY; 332 | // original element position 333 | obj.originalX = ia.offset.left; 334 | obj.originalY = ia.offset.top; 335 | // adjusted element position 336 | obj.offsetX = event.pageX - ( dd.pageX - obj.originalX ); 337 | obj.offsetY = event.pageY - ( dd.pageY - obj.originalY ); 338 | // assign the drop targets information 339 | obj.drop = drag.flatten( ( ia.drop || [] ).slice() ); 340 | obj.available = drag.flatten( ( ia.droppable || [] ).slice() ); 341 | return obj; 342 | }, 343 | 344 | // determine is the argument is an element or jquery instance 345 | element: function( arg ){ 346 | if ( arg && ( arg.jquery || arg.nodeType == 1 ) ) 347 | return arg; 348 | }, 349 | 350 | // flatten nested jquery objects and arrays into a single dimension array 351 | flatten: function( arr ){ 352 | return $.map( arr, function( member ){ 353 | return member && member.jquery ? $.makeArray( member ) : 354 | member && member.length ? drag.flatten( member ) : member; 355 | }); 356 | }, 357 | 358 | // toggles text selection attributes ON (true) or OFF (false) 359 | textselect: function( bool ){ 360 | $( document )[ bool ? "unbind" : "bind" ]("selectstart", drag.dontstart ) 361 | .attr("unselectable", bool ? "off" : "on" ) 362 | .css("MozUserSelect", bool ? "" : "none" ); 363 | }, 364 | 365 | // suppress "selectstart" and "ondragstart" events 366 | dontstart: function(){ 367 | return false; 368 | }, 369 | 370 | // a callback instance contructor 371 | callback: function(){} 372 | 373 | }; 374 | 375 | // callback methods 376 | drag.callback.prototype = { 377 | update: function(){ 378 | if ( $special.drop && this.available.length ) 379 | $.each( this.available, function( i ){ 380 | $special.drop.locate( this, i ); 381 | }); 382 | } 383 | }; 384 | 385 | // share the same special event configuration with related events... 386 | $special.draginit = $special.dragstart = $special.dragend = drag; 387 | 388 | })( jQuery ); -------------------------------------------------------------------------------- /examples/zoom/zoom.js: -------------------------------------------------------------------------------- 1 | window.zoom = o_O(1) 2 | 3 | var View = o_O.model({ 4 | pos: 500 5 | }, { 6 | initialize: function(o) { 7 | this.rects = o_O.array() 8 | }, 9 | scrollLeft: function() { 10 | var left = this.pos() * zoom() 11 | return left 12 | }, 13 | onbind: function() { 14 | var self = this 15 | $(this.el) 16 | .drag('start', function(e, dd) { 17 | dd.startPos = self.pos() 18 | dd.zoom = zoom() 19 | dd.w = (dd.startX - $('#container').offset().left) 20 | dd.s = dd.pos * dd.zoom 21 | }) 22 | .drag(function(e, dd) { 23 | var ratio = 1+dd.deltaY/100 24 | var z = dd.zoom * ratio 25 | zoom(z) 26 | var pos = -dd.deltaX/z + dd.w*(ratio - 1)/z + dd.startPos 27 | self.pos(pos) 28 | }) 29 | 30 | } 31 | }).bind('#container') 32 | 33 | 34 | var Rect = o_O.model.extend({ 35 | x: 0, 36 | y: 0, 37 | width: 100, 38 | height: 100, 39 | title: 'Rect' 40 | }, { 41 | css: function() { 42 | return { 43 | top: this.y(), 44 | left: this.x() * zoom(), 45 | width: this.width() * zoom(), 46 | height: this.height() 47 | } 48 | }, 49 | onbind: function() { 50 | var self = this 51 | $(this.el) 52 | .drag('start', function(e, dd) { 53 | dd.x = self.x() 54 | dd.y = self.y() 55 | }) 56 | .drag(function(e, dd) { 57 | self.x(dd.x + dd.deltaX/zoom()) 58 | self.y(dd.y + dd.deltaY) 59 | }) 60 | } 61 | }) 62 | 63 | 64 | for(var i =0; i< 30; i++) { 65 | View.rects.push( Rect({x: i*60, y: i%3 * 150 + 100, title: i}) ) 66 | } -------------------------------------------------------------------------------- /ideas/o_O.list.js: -------------------------------------------------------------------------------- 1 | /* ___ _ _ _ 2 | ___ / _ \ | (_)___| |_ 3 | / _ \ | | | || | / __| __| 4 | | (_) | | |_| || | \__ \ |_ 5 | \___/___\___(_)_|_|___/\__| 6 | |_____| */ 7 | 8 | !function() { 9 | 10 | function list(models) { 11 | if(this.constructor != list) return new list(models) 12 | 13 | this.objects = {} 14 | this.count = o_O(0) 15 | 16 | o_O.extend(this, o_O.Events) 17 | if(models) { 18 | for(var i=0; i< models.length;i++) { 19 | this.add(models[i]) 20 | } 21 | } 22 | } 23 | 24 | var proto = list.prototype 25 | 26 | proto.genId = function() { 27 | return ++this.genId.id 28 | } 29 | proto.genId.id = 0 30 | 31 | proto.add = function(o) { 32 | o.id = o.id || this.genId() 33 | this.objects[o.id] = o 34 | 35 | o.list = this 36 | 37 | if(o.on) { 38 | o.on('all', this._onevent, this) 39 | o.emit('add', o, this) 40 | } 41 | else 42 | this.emit('add', o) 43 | 44 | this.count.incr() 45 | } 46 | 47 | proto._onevent = function(ev, o, list) { 48 | if ((ev == 'add' || ev == 'remove') && list != this) return 49 | if (ev == 'destroy') { 50 | this.remove(o) 51 | } 52 | this.emit.apply(this, arguments) 53 | } 54 | 55 | proto.filter = function(fn) { 56 | var ret = []; 57 | this.each(function(o) { 58 | if(fn(o)) ret.push(o) 59 | }) 60 | return ret 61 | } 62 | 63 | proto.find = function(i) { 64 | return this.objects[i] 65 | } 66 | 67 | proto.each = proto.forEach = function(fn) { 68 | this.count(); // force the dependency 69 | for(var i in this.objects) 70 | fn.call(this, this.find(i), i) 71 | } 72 | 73 | proto.remove = function(o) { 74 | if(undefined === this.objects[o.id]) 75 | return 76 | delete this.objects[o.id] 77 | this.count.incr(-1) 78 | 79 | if(this == o.list) delete o.list 80 | if(o.off) { 81 | o.emit('remove', o, this) 82 | o.off('all', this._onevent, this) 83 | }else{ 84 | this.emit('remove', o) 85 | } 86 | } 87 | 88 | proto.renderOne = function(item, $el) { 89 | $(getTemplate($el)).each(function(i, elem) { 90 | var $$ = $(elem) 91 | $$.appendTo($el) 92 | o_O.bind(item, $$) 93 | }) 94 | } 95 | 96 | proto.bind = function($el) { 97 | var self = this 98 | 99 | this.on('add', function(item) { 100 | self.renderOne(item, $el) 101 | }) 102 | 103 | this.on('remove', this.removeElement, this) 104 | } 105 | 106 | proto.removeElement = function(item) { 107 | $(item.el).remove() 108 | } 109 | 110 | proto.toString = function() { 111 | return '#' 112 | } 113 | 114 | proto.extend = function() { 115 | return inherits(this) 116 | } 117 | 118 | o_O.list = list 119 | 120 | }() -------------------------------------------------------------------------------- /ideas/o_O.memory_store.js: -------------------------------------------------------------------------------- 1 | o_O.memoryStore = function() { 2 | this._id = 0 3 | this.objects = {} 4 | this.types = {} 5 | } 6 | 7 | var proto = o_O.memoryStore.prototype 8 | 9 | proto.uniqueId = function() { 10 | return ++this._id 11 | } 12 | 13 | proto.find = function(id) { 14 | return this.objects[i] 15 | } 16 | 17 | proto.add = function(object) { 18 | var id = object.id 19 | if(id == null || !this.find(id) ) { 20 | object.id = id || this.uniqueId() 21 | this.objects[id] = object 22 | } 23 | } 24 | 25 | proto.remove = function(o) { 26 | delete this.objects[o.id] 27 | } 28 | 29 | // merge in some methods to o_O.model 30 | o_O.model.protoype.store = new o_O.memoryStore() 31 | o_O.model.protoype.save = function() { 32 | this.store.add(this) 33 | } 34 | 35 | // o_O.model already acts as a factory 36 | // doesn't makes sense to move this to o_O.store 37 | o_O.model.types = {...} 38 | o_O.model.create = function(){ ... } // perhaps better to call something like `instantiate` - although a bit long 39 | 40 | // how do deal with loading id's and so should update the _id so that the unique id 41 | // whenever adding an object - check to see if object.id >= this._id ? -------------------------------------------------------------------------------- /o_O.collection.js: -------------------------------------------------------------------------------- 1 | /* * * * 2 | * o_O.collection 3 | * Simple example code to extend o_O.array into a hash like object 4 | * objects can be added/removed (though not pushed/popped) 5 | * maintains a fast lookup by id 6 | */ 7 | 8 | !function() { 9 | 10 | o_O.collection = o_O.array.extend({ 11 | type: 'o_O.collection', 12 | initialize: function() { 13 | var byId = this.byId = {} 14 | this.each(function(o) { 15 | byId[o.id()] = o 16 | }) 17 | this.on('remove', function(o) { 18 | delete byId[o.id()] 19 | }) 20 | }, 21 | push: null, 22 | shift: null, 23 | unshift: null, 24 | pop: null, 25 | add: function(o) { 26 | this.insert(o, this.length) 27 | this.byId[o.id()] = o 28 | }, 29 | findById: function(id) { 30 | return this.byId[id] 31 | } 32 | }) 33 | 34 | }(); -------------------------------------------------------------------------------- /o_O.js: -------------------------------------------------------------------------------- 1 | !function() { 2 | 3 | /* HTML binding for Lulz 4 | ,ad8888ba, 5 | d8"' `"8b Power of KnockoutJS, with the agility of Backbone 6 | d8' `8b 7 | ,adPPYba, 88 88 Elegantly binds objects to HTML 8 | a8" "8a 88 88 9 | 8b d8 Y8, ,8P Proxies through jQuery (or whatever $ is) 10 | "8a, ,a8" Y8a. .a8P 11 | `"YbbdP"' `"Y8888Y"' Automatic dependency resolution 12 | 13 | 888888888888 Plays well with others 14 | 15 | (c) 2012 by Jonah Fox (weepy), MIT Licensed */ 16 | 17 | var VERSION = "0.2.7" 18 | , slice = Array.prototype.slice 19 | , Events = { 20 | /* 21 | * Create an immutable callback list, allowing traversal during modification. The tail is an empty object that will always be used as the next node. 22 | * */ 23 | on: function(events, callback, context) { 24 | var ev; 25 | events = events.split(/\s+/); 26 | var calls = this._callbacks || (this._callbacks = {}); 27 | while (ev = events.shift()) { 28 | 29 | var list = calls[ev] || (calls[ev] = {}); 30 | var tail = list.tail || (list.tail = list.next = {}); 31 | tail.callback = callback; 32 | tail.context = context; 33 | list.tail = tail.next = {}; 34 | } 35 | return this; 36 | }, 37 | 38 | /* 39 | * Remove one or many callbacks. If context is null, removes all callbacks with that function. 40 | * If callback is null, removes all callbacks for the event. 41 | * If ev is null, removes all bound callbacks for all events. 42 | * */ 43 | off: function(events, callback, context) { 44 | var ev, calls, node; 45 | if (!events) { 46 | delete this._callbacks; 47 | } else if (calls = this._callbacks) { 48 | events = events.split(/\s+/); 49 | while (ev = events.shift()) { 50 | node = calls[ev]; 51 | delete calls[ev]; 52 | if (!callback || !node) continue; 53 | 54 | // Create a new list, omitting the indicated event/context pairs. 55 | 56 | while ((node = node.next) && node.next) { 57 | if (node.callback === callback && 58 | (!context || node.context === context)) continue; 59 | this.on(ev, node.callback, node.context); 60 | } 61 | } 62 | } 63 | return this; 64 | }, 65 | /* 66 | * Trigger an event, firing all bound callbacks. Callbacks are passed the same arguments as emit is, apart from the event name. 67 | * Listening for "*" passes the true event name as the first argument. 68 | * */ 69 | emit: function(events) { 70 | var event, node, calls, tail, args, all, rest; 71 | if (!(calls = this._callbacks)) return this; 72 | all = calls['all']; 73 | (events = events.split(/\s+/)).push(null); 74 | 75 | // Save references to the current heads & tails. 76 | while (event = events.shift()) { 77 | if (all) events.push({next: all.next, tail: all.tail, event: event}); 78 | if (!(node = calls[event])) continue; 79 | events.push({next: node.next, tail: node.tail}); 80 | } 81 | 82 | //Traverse each list, stopping when the saved tail is reached. 83 | 84 | rest = slice.call(arguments, 1); 85 | while (node = events.pop()) { 86 | tail = node.tail; 87 | args = node.event ? [node.event].concat(rest) : rest; 88 | while ((node = node.next) !== tail) { 89 | node.callback.apply(node.context || this, args); 90 | } 91 | } 92 | return this; 93 | } 94 | } 95 | 96 | 97 | /* 98 | * Public function to return an observable property 99 | * sync: whether to emit changes immediately, or in the next event loop 100 | */ 101 | var propertyMethods = { 102 | incr: function (val) { return this(this.value + (val || 1)) }, 103 | scale: function (val) { return this(this.value * (val || 1)) }, 104 | toggle: function (val) { return this(!this.value) }, 105 | change: function(fn) { 106 | fn 107 | ? this.on('set', fn) // setup observer 108 | : this.emit('set', this(), this.old_val) 109 | return this 110 | }, 111 | mirror: function(other, both) { 112 | var self = this 113 | other.change(function(val) { 114 | if(val != self.value) self(val) 115 | }).change() 116 | 117 | both && other.mirror(this) 118 | return this 119 | }, 120 | toString: function() { 121 | return '<' + (this.type ? this.type + ':' : '') + this.value + '>' 122 | }, 123 | bind: function(el) { 124 | o_O.bind(this, el) 125 | return this 126 | }, 127 | emitset: function() { 128 | if(this._emitting) return // property is already emitting to avoid circular problems 129 | this._emitting = true 130 | this.emit('set', this(), this.old_value) // force a read 131 | delete this._emitting 132 | }, 133 | // timeout: 0, 134 | constructor: o_O // fake this - useful for checking 135 | } 136 | 137 | function o_O(arg, type) { 138 | var simple = typeof arg != 'function' 139 | 140 | function prop(v) { 141 | if(arguments.length) { 142 | prop.old_value = prop.value 143 | prop.value = simple ? v : arg(v) 144 | prop.emitset() 145 | } else { 146 | if(dependencies.checking) 147 | dependencies.emit('get', prop) // emit to dependency checker 148 | if(!simple) 149 | prop.value = arg() 150 | } 151 | return prop.value 152 | } 153 | 154 | if(simple) 155 | prop.value = arg 156 | else { 157 | prop.dependencies = [] 158 | each(dependencies(prop), function(dep) { 159 | prop.dependencies.push(dep) 160 | dep.change(function() { 161 | prop.emitset() 162 | }) 163 | }) 164 | } 165 | 166 | extend(prop, Events, propertyMethods) 167 | if(type) prop.type = type 168 | return prop 169 | } 170 | 171 | /* 172 | * Calculate dependencies 173 | */ 174 | function dependencies(func) { 175 | var deps = [] 176 | function add(dep) { 177 | if(indexOf(deps, dep) < 0 && dep != func) 178 | deps.push(dep) 179 | } 180 | dependencies.checking = true // we're checking dependencies 181 | dependencies.on('get', add) // setup listener 182 | dependencies.lastResult = func() // run the function 183 | dependencies.off('get', add) // remove listener 184 | dependencies.checking = false // no longer checking dependencies 185 | return deps 186 | } 187 | extend(dependencies, Events) 188 | 189 | o_O.dependencies = dependencies 190 | 191 | 192 | // returns a function from some text 193 | o_O.expression = function(text) { 194 | o_O.expression.last = text // remember the last case useful for debugging syntax errors 195 | return new Function('o_O', 'with(this) { return (' + text + '); } ') 196 | } 197 | 198 | o_O.nextFrame = (function() { 199 | var fns = [] 200 | , timeout; 201 | 202 | function run() { 203 | while(fns.length) fns.shift()() 204 | timeout = null 205 | } 206 | 207 | var self = this 208 | // shim layer with setTimeout fallback 209 | var onNextFrame = self.requestAnimationFrame || 210 | self.webkitRequestAnimationFrame || 211 | self.mozRequestAnimationFrame || 212 | self.oRequestAnimationFrame || 213 | self.msRequestAnimationFrame || 214 | function( callback ) { 215 | self.setTimeout(callback, 1000 / 60); 216 | }; 217 | 218 | return function(fn) { 219 | fns.push(fn) 220 | timeout = timeout || onNextFrame(run) 221 | } 222 | 223 | })(); 224 | 225 | 226 | /* 227 | * el: dom element 228 | * binding: name of the binding rule 229 | * expr: text containing binding specification 230 | * context: the object that we're binding 231 | */ 232 | 233 | 234 | o_O.bindRuleToElement = function(method, expressionString, context, el) { 235 | var expression = o_O.expression(expressionString) 236 | , binding = o_O.createBinding(method) 237 | , $el = $(el) 238 | , value // contains the current value of the attribute that emit will use 239 | 240 | // if it's an outbound event - just emit immediately and we're done 241 | if(binding.type == 'outbound') { 242 | evaluateExpression() 243 | emit() 244 | return 245 | } 246 | 247 | // otherwise we need to calculate our dependencies 248 | var deps = dependencies(evaluateExpression) 249 | 250 | // if we're not two way and it's a function - then really it's a short hand for missing brackets - recalculate 251 | if(typeof value == 'function' && binding.type != 'twoway') { 252 | expression = o_O.expression('(' + expressionString + ')()') 253 | deps = dependencies(evaluateExpression) 254 | } 255 | 256 | // we should emit immediately 257 | emit() 258 | 259 | // and also everytime a dependency changes - but only once per binding per frame - even if > 1 dependency changes 260 | var emitting 261 | 262 | each(deps, function(dep) { 263 | dep.change(function() { 264 | evaluateExpression() 265 | if(emitting) return // don't queue another 266 | emitting = true 267 | o_O.nextFrame(function() { 268 | emit() 269 | emitting = false 270 | }) 271 | }) 272 | }) 273 | 274 | 275 | // evaluates the current expressionString 276 | function evaluateExpression() { 277 | value = expression.call(context, o_O.helpers); 278 | if(value instanceof String) value = value.toString(); // strange problem 279 | } 280 | 281 | // emit is the function that actually performs the work 282 | function emit() { 283 | return binding.call(context, value, $el) 284 | } 285 | } 286 | 287 | 288 | 289 | 290 | /* 291 | * Helper function to extract rules from a css like string 292 | */ 293 | function parseBindingAttribute(str) { 294 | if(!str) return [] 295 | var rules = trim(str).split(";"), ret = [], i, bits, binding, param, rule 296 | 297 | for(var i=0; i 390 | _| */ 391 | 392 | o_O.bindings = { 393 | /* 394 | * set visibility depenent on val 395 | */ 396 | visible: function(val, $el) { 397 | val ? $el.show() : $el.hide() 398 | }, 399 | 'if': function(context, $el) { 400 | var template = getTemplate($el) 401 | $el.html(context ? template : '') 402 | }, 403 | unless: function(val, $el) { 404 | return o_O.bindings['if'](!val, $el) 405 | }, 406 | 'with': function(context, $el) { 407 | var template = getTemplate($el) 408 | $el 409 | .html(context ? template : '') 410 | .children().each(function(i, el) { 411 | o_O.bind(context, el) 412 | }) 413 | }, 414 | options: function(options, $el) { 415 | var isArray = options instanceof Array 416 | $.each(options, function(key, value) { 417 | var text = isArray ? value : key 418 | $el.append($("