ractivejs / ractive Goto Github PK
View Code? Open in Web Editor NEWNext-generation DOM manipulation
Home Page: http://ractive.js.org
License: MIT License
Next-generation DOM manipulation
Home Page: http://ractive.js.org
License: MIT License
This doesn't work:
<svg xmlns='http://www.w3.org/2000/svg'>
<text>some text</text>
</svg>
Off the top of my head, probably because it's trying to use innerHTML
not sure if they are missing or simply not listed in the examples
If you have a template like this...
<select value='{{color}}'>
<option value='red' selected>Red</option>
<option value='green'>Green</option>
<option value='blue'>Blue</option>
</select>
...the value of color
should be initialised to red
. That's how it works with radios
Hey there, thanks for the awesome library. It's damn near everything I've been looking for!
I was wondering if there was any way to access the current value of the data property before it's updated?
My rationale is that it would open up running arbitrary operations on these templated values like incrementing counters instead of just replacing data.
I noticed this in the Anglebars.Data.prototype.set() data.js:
// find previous value
previous = this.get( address );
Maybe there's a way to do this?
Previously, on encountering a namespaced attribute, Ractive would search up the DOM tree until it found the corresponding namespace declaration. This no longer works because we're using DOM fragments.
It would probably make a lot more sense to use a hash of namespaces, since there aren't many (only xlink?) that we need to worry about - http://developers.whatwg.org/urls.html
i'm porting my app to use anglebars and in the previous render logic I had some templates that would silently fail if the target element wasn't in the DOM. I could insert some logic to check if they are in the DOM before I instantiate a new Anglebars instance but figured I'd ask if you think this behavior is expected or if its a bug
Not sure if it's working (or I'm missing on usage), I see it's implemented in the code...
Ideally it should be possible to pinpoint which part of a template (i.e. which character, or token) caused the parser to fail
See #6. It would be useful to be able to establish which part of the viewmodel (which todo, in the canonical example) is related to which part of the DOM. Need to figure out the best way to do this in a way that is a) performant (ie avoids too much DOM traversal) and b) philosophically coherent, ie doesn't turn Anglebars into a full-blown state management utility
As a convenience, Ractive stores references to nodes with id
attributes as ractive.nodes[id]
. This doesn't happen when relevant parts of the template don't contain mustaches, since everything is bunged in with innerHTML and there is no opportunity to identify nodes with an id
attribute.
Best solution is probably to mark such nodes as un-stringifiable at parse time.
Setting properties downstream of an array or object that has been modified doesn't result in the children of the modified object being updated. I.e. if you have a template like this
{{#list | uppercase}}
{{.}}
{{/list}}
with data like { list: [ 'foo', 'bar', 'baz' ] }
(where 'uppercase' uppercases each array member) then the result will look like
FOO
BAR
BAZ
If you then do view.set( 'list[2]', 'boo' )
nothing happens until the whole array is updated.
We need some way that modified objects can be made aware of changes to members of those objects (and their descendants).
Calling view.set('foo.bar', 'baz')
on a view with no data causes this viewmodel to be created:
{
"foo": {
"bar": "baz",
}
}
This is fine, however view.set('foo.bar[0]', 'baz')
results in
{
"foo": {
"bar": {
"0": "baz"
}
}
}
when it should ideally result in
{
"foo": {
"bar": ["baz"]
}
}
If there are two expressions that resolve identically, they shouldn't both be listed as dependencies of their keypaths - first come, first served. The second mustache will be taken care of by the first expression's evaluator.
Templates often contain lots of whitespace:
<div>
<p>This template contains unnecessary whitespace.</p>
</div>
The example above is functionally equivalent to
<div><p>This template contains unnecessary whitespace</p></div>
because browsers don't care about neat indentation, and unless you're using white-space: pre[-wrap];
multiple whitespace characters are collapsed.
So it would be useful to strip unnecessary whitespace out during the compilation step, unless a) the user has explicitly chosen to preserve whitespace, or b) the whitespace is inside a <pre>
element. This means
With the introduction of transitions there are a number of potentially-async operations (set, update, teardown etc). Would be nice if these returned promises, as they're generally nicer to work with than callbacks. Only question is whether it's worth the overhead
This compiles fine:
<div class='test'></div>
This doesn't, even though it's valid HTML:
<div class=test></div>
Ractive will try to compile any templates that are passed in as strings - if they are objects, it assumes they are valid precompiled templates.
This is fine unless the template is HTML (not SVG) and doesn't contain mustaches, since the compiled template will be a string, not an object (since this allows use of innerHTML). So at runtime Ractive tries to compile it again!
This should work:
<ul>
{{#items}}
<li>{{( doSomethingWith( . ) )}}</li>
{{/items}}
</ul>
Currently, it doesn't.
By default (and as per the mustache spec), if a property is not found in the current context, Ractive travels up the context stack until a reference can be resolved. This isn't always what you want (e.g. when using recursive partials).
This syntax could be made to mean 'only look for this property in the current context':
{{.name}}
In practice this means we can always resolve such a reference immediately - it's just context + reference
- which actually saves us some work.
If we have the following data
{ total_completed: 0,
todos: [{todo:'beep', completed:false}]
}
If I manually change the value of total_completed (on the underlying data), then tpl.update('total_completed') - template won't render the new value.
Pushing new values to the todos array, followed by tpl.update() works though.
I don't know if this violates the YAGNI principle, but it might be quite neat if references to ID'd nodes were stored, e.g. on a view's nodes
property:
<div class='box'>
<a id='close'>X</a>
{{{content}}}
</div>
view = new Ractive({
el: 'container',
template: myTemplate,
content: '<p>some content</p>'
});
console.log( view.nodes.close ); // logs the anchor with the X
Maybe class names could be arrays on the nodes
property, containing all the nodes of that class.
Just an idea. Easy to implement, question is whether it's necessary or not.
This is a common pattern:
<div class='dialog'>
<p>{{dialogText}}</p>
<a id='okay' class='button'>OK</a>
</div>
view = new Ractive({
el: '#container',
template: dialogTemplate,
data: { dialogText: 'Press OK to close this dialog' }
};
document.getElementById( 'okay' ).addEventListener( 'click', function () {
view.teardown();
});
This is fine, as far as it goes, and using jQuery or similar makes it slightly less verbose. But the fact that we're giving an element an ID just so we can easily find it a moment later and bind event handlers to it is a bit... yucky. It goes against the spirit of declarative programming to have to throw in these hooks. Sure, the ID could be more descriptive - 'closeDialog' or something - but that's not really what IDs are for. Our abstractions are leaking.
It would be nicer to be able to do something like this:
<div class='dialog'>
<p>{{dialogText}}</p>
<a on-click='closeDialog' class='button'>OK</a>
</div>
view = new Ractive({
el: '#container',
template: dialogTemplate,
data: { dialogText: 'Press OK to close this dialog' }
};
view.on( 'closeDialog', function () {
this.teardown();
});
That way things are better separated. It also allows us to define multiple behaviours in a nicer way:
view.on({
collapse: function () {
// code goes here
},
expand: function () {
// code goes here
}
});
Some additional thoughts right off the bat: it would be useful to pass along the event data, and also the element that was the subject of the DOM event (i.e. the event's target or one of its ancestors). Normally with event handlers, this
is the element, but within a view.on()
handler this === view
, and it probably isn't a good idea to change that. A good compromise would be to pass along the element as an argument:
view.on( 'open', function ( event, el ) {
var target;
if ( event.shiftKey ) {
el = el.getAttribute( 'data-target' ); // or whatever
// do something
}
});
So. The real problem to figure out here is what syntax to use in templates to make this happen. It's probably best to use an attribute, as many editors barf if you start throwing illegal characters around inside a document. The colour coding gets messed up. That matters, because the moment that starts happening everything begins to feel like a hack.
For that reason I'm not all that keen on something like Ember's {{action}}
helper.
Angular does it using an ng-click
attribute (though rather than an event label, you're using pseudo-JavaScript which corresponds to a method on the relevant controller's $scope object). This isn't a bad solution - it means the template doesn't validate as HTML, but who really cares? We could do something similar with rv-click
, or something. I'm not in love with the aesthetics though.
One thing we definitely shouldn't do is use an on-click
attribute or similar, example above notwithstanding. That's too similar to onclick
where the value is eval'ed as JavaScript in the global context. Ugly, ugly, ugly stuff.
Two final considerations:
{{#todos:i}}
<li data-index="{{i}}" class="{{#completed}}completed{{/completed}}{{^completed}}view{{/completed}}"></li>
{{/todos}}
the above will always render class="", whereas as the same section renders correctly inside the li tag.
The fix seems to have solved the initial rendering issue:
<li data-index="{{i}}" class="{{#completed}}completed{{/completed}}{{^completed}}view{{/completed}}">
</li>
Initial output is correct (first render), but if .completed changes, class attribute in the above example doesn't re-render.
This is specific to attributes because it works if we take the same content of class attribute and place it inside the li tag itself.
PS: Hope I'm not driving you crazy with me I'm working with Angelsbars that's why I'm seeing this ;)
set
fires at the start of a set cycle, but update
fires afterwards. Need some consistency! Also, what about smart updates?
Attempting to use an expression like {{#( sort( list, 'name' ) )}}
throws an error. {{#( sort( list, "name" ) )}}
, on the other hand, works just fine.
We could happily remove whitespace-only text nodes at the beginning or end of a template. We could probably also remove whitespace between certain elements (e.g. newlines between divs) but there are lots of rules here that I don't fully understand...)
It feels like the way priorities are expressed in the dependency graph is backwards. Instead of listing priorities by dependency, shouldn't we be listing dependencies by priority?
This syntax would be much nicer to deal with than {{#condition}}iftrue{{/condition}}{{^condition}}iffalse{{/condition}}
. Real world use case - todo list:
<li class='{{done ? done : pending}}'><!-- etc --></li>
Obviously not part of mustache, but would be a useful extension.
Question: should the values be quoted?
For quickly setting up and testing things, without faffing about with AJAX/requirejs/etc, it would be useful to be able to refer to templates embedded on the page like so:
<script type='text/template' id='myTemplate'>
<!-- template content goes here -->
</script>
This could then perhaps be referenced by passing in template: '#myTemplate'
as an initialisation option
It would be good to be able to configure Ractive to work alongside any event-emitting data source (like Ractive Couch but with any back-end/model).
Inspired by Rivets.js, I like the idea of using adaptors, where the adaptor is a simple layer that wraps around the source's API.
This is fairly simple in concept. The trick will be making it easy to create new adaptors.
a.formatters.lower = function (input){
return input.toLowerCase();
};
The above seem to run only once, i.e. if the value of "input" changes later the above will not be re-rendered. This used to work in the previous version.
If you do
Subclass = Ractive.extend({
template: someTemplate,
data: { foo: 'one' }
});
view = new Subclass({
el: '#container',
data: { bar: 'two' }
});
you would expect view.get()
to return { foo: 'one', bar: 'two' }
. Instead it gets overwritten, so it returns { bar: 'two' }
.
This is a side-effect of the lazy way Ractive initialises - all properties of the options
argument get added to the instance. Should probably fix that.
See #6. We want to be able to add or remove element attributes (rather than merely changing their value) when parts of the viewmodel change, the obvious example being
<input type='checkbox' {{#complete}}checked{{/complete}}>
or even
<input type='checkbox' {{checked}}>
.anglebars-anchor { display: none !important; }
they were effing up my layout, is it okay to hide them like this?
Ping!
Just noticed Ractive hit 3.0! Congratulations Rich! Looking forward to seeing what's under the hood! Already noticed proxy-event="" -> awesome, means I can get rid of my custom implementation (x-bind="" then I add event listeners once the page has rendered).
Ideas for future implementations:
Keep it up!
There is an upper limit to the complexity of applications that Ractive can be used for without some more sophisticated dependency tracking, a la Knockout, Statesman or Reactor.
Right now the only form of dependency tracking is downstream keypaths (e.g. foo.bar
) depending on their upstream keypaths (e.g. foo
).
Better dependency tracking could manifest itself in two ways - computed values which are not considered 'downstream' of a particular keypath, and modifiers which have one obvious dependency (their upstream value) but can have other dependencies.
Knockout and Reactor track dependencies by watching for model.get( property )
calls within functions. This is really clever but I'm not totally sure I like it, it's a bit voodoo-y and requires that dependencies be re-tracked each time the function is called (though if the computing function has lots of branching logic that may be a win).
The alternative is to make dependencies explicit somehow. In Statesman computed values are described as e.g. ${x} + ${y}
- the dependencies are extracted using regex - or declared as a dependsOn
property.
Not totally sure at this stage what direction to head in.
Angelbars is AWESOME!! Well done!!
I got Angelbars to render server-side using jsdom for serving static content to crawlers, to do that I simply passed a jsdom document to the constructor options and declare a global variable "document". The change is too small to send a PR request:
if ('undefined' === typeof document) var document; (global - alternatively at the environment check at the bottom of the script)
if (options.document) document = options.document; (in the constructor)
I'm working on a framework for building P2P-based, realtime single-page and intending to use Angelbars; let me know if you're interested in seeing this in action ;)
It would be useful to have the option of sanitising templates, particularly for cases involving user input. These elements could potentially be put to nefarious ends:
Should be fairly straightforward to remove these during the compile step
If you click on the checkbox, you can no longer uncheck it
<input type="text" value="">
Rendering the above will fail, giving the following error:
TypeError: Cannot call method 'toLowerCase' of undefined.. at Anglebars.js:1355:70)
That attribute is there, but value is an empty string
{
"name": "Section with index refs",
"template": "<ul>{{#items:i}}<li data-index="{{i}}">{{name}}</li>{{/items}}</ul>",
"data": {
"items": [{"name":"zero"},{"name":"one"},{"name":"two"}]
},
"result": "<ul><li data-index="">zero</li><li data-index="">one</li><li data-index="">two</li></ul>"
}
Notice I moved the {{i}} from the original test and placed it in an attribute data-index
Need to include JSON2 with _legacy.js? JSON.parse is used with transition parameters
This idea is born out of using ractive for about a day. So feel free to shoot it down in flames.
Once challenge I have noticed is managing large lists. When updates/deletes come in, one has to find the row in an array that matches the update so one can view.set('row[0]', update).
One idea to make things easier would be to use the id that these objects have (from a datastore) and allow the list directive to iterate over object properties. For example, one would map data back from a db like this
view = new Ractive({
data: {
rows: {
'434342' : { id: '434342', name: 'Ryan' },
'434343' : { id: '434343', name: 'Rich' }
}
}
});
and the template
<ul>
{{#rows}}
<li data-id={{key}}>{{value.name}}</li>
{{/rows}}
</ul>
and to do an update
view.set('data.rows["434342"]', {id: '434342', name: 'Rob'});
This is non-standard mustache but I think it makes sense in terms of allowing data-binding with large lists.
We can theoretically control how elements are rendered and torn down. For example on the todo list demo it would be neat if new todos could slide in, and removed ones could fade out, or something.
Or a sequence of images could have a staggered fade in. Fantasy code:
{{#images:i}}
<img class='thumb' src='{{src}}' data-index='{{i}}' intro='staggeredFade'>
{{/images}}
.thumb {
transition: 1s opacity;
}
ractive = new Ractive({
el: container,
template: theTemplate,
data: { images: someImages },
transitions: {
staggeredFade: function ( el ) {
var index = +( el.getAttribute( 'data-index' ) );
setTimeout( function () {
el.style.opacity = 1;
}, i * 100 );
}
}
});
Of course this is quite a naive example - you might want different transitions at different stages of the app (initially you might want the staggering, but if new images were added after that then maybe you'd want them to appear straight away, for example).
CSS animations should be the default, but being notified of transition ends tends to be a bit of a ballache (though that's an opportunity for Ractive to take care of some unpleasant grunt work for developers, if we can find a nice way of doing something like ractive.set( keypath, value, transitionEndHandler )
. That requires further thought.)
We'd also want to change the way lists behave. At present, if you mutate an array, the wrapped mutator methods call ractive.set( keypath, myself )
with whatever keypaths (and indeed whatever Ractives) the array is currently bound to. That means that if you do list.shift()
, instead of removing the zeroth section fragment, it removes the last one, and updates all the fragments in between so that it looks like the top item was removed.
The reason for this is that updating all the dependency tracking would be a bit of a ballache. However it's almost certainly the Right Thing To Do. If we can save ourselves some DOM manipulation, that will pretty much obliterate the overhead of reassigning dependencies, as well as enabling transitions.
That's fine for lists updated using array mutator methods - situations where an array gets updated and set
or update
d are a little tougher. How to we know which section fragment belongs to which list item? This also requires further thought.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.