Giter Site home page Giter Site logo

bind.js's Introduction

Bind.js

Two way data binding for HTML and JavaScript (with node.js compatibility) with additional support for transforming data before it arrives in the DOM.

npm version Coverage Status

Demos

Prerequisites

setters/gettings, fn.bind, qSA (if using selectors), getOwnPropertyNames.

Usage

Create a new Bind based on an object and a mapping. The mapping uses a key/value pair of property path to handler. The handler can be a CSS selector (and thus updates the DOM) or a callback.

There is also an advanced value that allows finer grain control over the binding (seen in the skills value in the example below).

Browser version can be downloaded from releases or via a CDN like unpkg: https://unpkg.com/bind.js/dist/bind.min.js

The node version can be installed using npm install -S bind.js.

Example (JS Bin)

var player = Bind({
  name: '@rem',
  score: 11,
  location: {
    city: 'Brighton',
    country: 'England'
  },
  skills: [
    'code',
    'chicken',
    'shortness'
  ]
}, {
  score: '#score',
  name: '#name',
  'location.city': function (city) {
    alert(this.name + "'s city is " + city);
  },
  skills: {
    dom: '#skills',
    transform: function (value) {
      return '<li>' + this.safe(value) + '</li>';
    },
  }
});

document.querySelector('form').onsubmit = function (event) {
  event.preventDefault();
  player.skills.push(document.querySelector('#newSkill').value);
  this.reset();
};

Notice that in the second argument to Bind the mapping key is a path to the object property separated by a . period: 'location.city': function.

Mapping values

Mapping values can be:

  • String: a CSS expression
  • Function: a callback that receives the new value
  • Object: see below

If the mapping value is an object, all the following properties are supported:

  • dom: a string CSS expression
  • callback: a function
  • transform: a function that receives the new value, and returns the HTML that will be set to the DOM.
  • parse: a function that receives the changed value from the DOM and returns the value that will be set in the JavaScript object

Note that the transform function is applied to array elements when mapped to an array, and so does not receive the entire array. This is to allow control over updating lists in the DOM (see the example above).

Arrays

Individual array elements can be also mapped using the dot notation and the index in the array.

In the example below, when the first cat name in the array changes, it will update the DOM.

var data = Bind({
  cats: ['ninja', 'missy', 'dizzy']
}, {
  cats: {
    dom: 'ul',
    transform: function (name) {
      return '<li>' + name + '</li>';
    }
  },
  'cats.0': '#first-cat'
});

// later let's add Sam to the cats
data.cats.unshift('sam');

Using the DOM to inform values

If you want the DOM to drive the initial values of the bind object, then you'll need to set the JavaScript property value to null and it will read the value from the DOM at startup:

var data = Bind({
  price: null
}, {
  price: '.price',
});

Now in the HTML:

<p class="price">£10.50</p>

Now data.price has the value of £10.50. If you wanted this to be a float instead, you would use the parse and transform methods:

var data = Bind({
  price: null
}, {
  price: {
    dom: '.price',
    parse: function (v) {
      return parseFloat(v.replace(//, ''), 10);
    },
    transform: function (v) {
      return '£' + v.toFixed(2);
    }
});

Now data.price is 10.5, and when the value is changed to data.price = 11.5, the DOM is updated to £11.50.

Restrictions

Deleting primitive property

There's no handling deleted primitive properties. Once it is deleted, if it's added back in again, it can't be tracked:

data.me.score++; // updates element#score
delete data.me.score;
data.me.score = 1; // does nothing

// A work around is to restore the property object, the following
// re-uses the bind map, and updates element#score again
data.me = {
  score: 1,
  // ... etc
};

Events at the root object

This isn't currently supported, but could be implemented with a special mapping - I'm open to suggestions here.

Otherwise, the object can be nested and callbacks be bound to the first depth property (as seen in the forms example)

Exporting

If the original, unbound object is needed, a utility function is available on the root object:

var copy = data.__export();

License

MIT / http://rem.mit-license.org

bind.js's People

Contributors

bronzehedwick avatar daniel-hug avatar kant avatar remy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bind.js's Issues

Bind <Select>

I am trying to bind the values of an array to the input values of a select, similar to how you bind "cats" to a UL in the playground. If I assign the array to a UL with the function :

dom:'#cats',
transform: function (item){return ""+item+"";}

and #cats is a UL, the the values are printed.

If I change #cats to a SELECT I get no love. any ideas?

Thank you

API to dynamically add and remove bindings

The Bind() function creates an "observable Object" (the return value from the function) and sets up bindings to other Objects (often DOM nodes). But AFAICT there's no way to dynamically add or remove bindings to an existing "observable Object". Something like:

var user = Bind({ first: "Bob", last: "Jones"});
user.bind("first", "#first");
user.bind("last", "#last");

The reason that would be nice is for code decoupling, i.e. so various pieces of code that don't know about each other can attach to user.

Interesting bug

Pease check: http://jsbin.com/kahecu/edit?js,output

var data = Bind({
  score: 10,
  cats: ['dizzy', 'missy', 'ninja'],
}, {
  score: '.score',
  cats: updateCats
});

setInterval(function () {
  data.score++;
    //data.cats[1] = Math.random(); // !!! IF THIS LINE UNCOMMENTED IT'S CORRECTLY WORKING !!!
    data.cats[3] = Math.random();
  if (data.score > 100) {
    data.score = 0;
  }
}, 500);

function updateCats(list, old) {
  document.querySelector('#cats').innerHTML = '<li>' + list.join('<li>');
}

data.cats.push('sil')

Circular references in arrays blows up

Been using bind in a private node project, and this causes a pretty serious bail:

data = new Bind({
  foo: [],
}, {
  foo: function () {},
});

data.foo.push(data.foo); // RangeError: Maximum call stack size exceeded

Multiple unnecessary dom updates

example of the side effect that i'am talking about is shown in this example below
https://jsbin.com/fomaminoza/1/edit?html,js,console,output

(typeof settings.mapping[path.join('.')] != 'undefined') 

I strongly believe that only mapped values should trigger the callback.

that brings me to the conclusion that objects have the same problem:

{
me.price: {net:100, tax: 1.19}
}, {
'me.price': {
 dom:'div#somediv',
 transform: function(obj) {
     //bind.js inject the whole me.price object ! 
    return obj.net*obj.tax;
}
}
}

transform is triggered for each object property and is't parent's instead of only it's bound properties .
I have made a fix for the init state, but not for that object issue.

Karma tests on remote shell (Cloud9)

@remy I wonder if you have any experience running the karma tests headless (without an X server) for this package ? If it's feasible, I would like to run the browser tests on my remote shell.

Modifications over dom

I work with multiple forms, and i use to disable and hide a lot of fields.
Would be nice to be able to access the dom via model, so the code could be much more cleaner.

My (maybe imposible) idea, is to access like
myModel.firstField.el.prop('disabled', true)
If 'el' returns the jQuery element of the dom linked to the field. Maybe
$(myModel.firstField.el).prop('disabled, true)
If jQuery is nasty inside the bind.js

By now, I've changed it to
this.el = function (target) { if(typeof settings.mapping[target] === o) { return jQuery(settings.mapping[target].dom); } else { return jQuery(settings.mapping[target]); } };
What results in
myModel.el('firstField').prop('disabled', true)
But im not really happy with it... in fact, it hurts... Also, the __export function, returns 'el' as a property... but I cannot dig into the code that easy...
If this is not useful for all, at least, how to do it right?

Table Example

Do you have a quick example that shows how one might bind a data array to an html table?

Binding to an existing select wipes out options...

I am using the following code to loop over my form fields and generate config for Bind...

`console.clear();
var prefix = '#form-product-p2p-filter ';
var expr = /^product_(attribute|discount|special)[\d+]/,
form = $('#form-product-p2p-filter').serializeArray().filter(function (obj) {
return !obj.name.match(expr);
});

var viewmodel = {},
    bindings = {};

$.each(form, function (idx, item) {
    var selector = prefix + '[name=' + item.name + ']',
        el = $(selector),
        type = el[0].tagName.toLowerCase(),
        value = null;

    if (type === 'select' || type === 'checkbox') {
        if (type === 'select') {
            //value = $(selector + ' option:selected').text();
            //console.log(value);
        } else {
            //value = el.val();
        }

    }

    viewmodel[item.name] = value; // Pull values from form on init
    bindings['basic.' + item.name] = selector;
});

var output = null;

bindings['basic'] = { callback: function () {
    console.log(output);
}};

var config = {
    questions: {
        has_shipping: null,
        has_vendor: null,
        has_dimensions: null,
        is_bookable: null
    },
    basic: viewmodel
};

console.log(config);
console.log(bindings);

output = Bind(config, bindings);`

For some reason, the binding works find on regular input elements, but all the options in all my select elements are getting wiped out. Any ideas?

BTW -- downgrading to v 1.0.1 fixed my issues.

The automated release is failing 🚨

🚨 The automated release from the master branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this 💪.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


Invalid npm token.

The npm token configured in the NPM_TOKEN environment variable must be a valid token allowing to publish to the registry https://registry.npmjs.org/.

If you are using Two-Factor Authentication, make configure the auth-only level is supported. semantic-release cannot publish with the default auth-and-writes level.

Please make sure to set the NPM_TOKEN environment variable in your CI with the exact value of the npm token.


Good luck with your project ✨

Your semantic-release bot 📦🚀

Initial DOM values bleed into each other

Consider this markup

<p class="price">£10.50</p>
<span class="price">none</span>
 <ul>
     <li class="price"></li>
 </ul>
 <button id="btn">test</button>

The above is then bound as follows:

$(document).ready(function() {
    var data = Bind({
        price: null
    }, {
        price: {
            dom: '.price',
            parse: function(v) {
                return parseFloat(v.replace(/^£/, ''), 10);
            },
            transform: function(v) {
                return '£' + v.toFixed(2);
            }
        }
    });

    $('#btn').on('click', function(e) {
        data.price = 112.052;
    });
});

This does not work as expected because the LI and the SPAN elements both show the P tags initial value of 10.50.

Is there a workaround for this? Thanks

Cant get dates to bind correctly

If I want to show dates in a table it seems that Bind is converting the Date Object to a normal Object and it is losing all of its methods (getFullYear() etc).

https://jsbin.com/sezuciceya/edit?html,js,console,output

It show [Object object] in the table but the span show the right value, if you change the transform from:

return '<tr><td>' + this.safe(cat.name) + "</td><td>" + cat.dob+'</td></tr>' ;

to

return '<tr><td>' + this.safe(cat.name) + "</td><td>" + cat.dob.getFullYear()+'</td></tr>' ;

All you get is undefined.

Hitting update the value of Cats[0].dob also gets "lost in translation".

What am I doing wrong?

Different transformations per target

Hi, I have a config like this

bind({
  date: null,
}, {
  date: 'input[name=date],div#display__date',
});

and would like to apply different transformation functions to the input and div. So that the value of the input is still valid, but in the div it gets displayed as a human readable string.

Is this possible with the current implementation?

Edit: I'm currently thinking about adding a third argument to the transform call "element" that represents the currently targeted DOM Element. That way I can decide how to transform the value. What do you think?

Calculator Example

Hi there

Noob question: Is there a way to use bind.js to build a calculator using form elements like an input range slider, checkbox etc.?

Thanks!

[low] Comment header

Would it be possible to add a comment header to the build version of bind.js (or even include this to the dev version as well)?

I've taken the liberty to write a sample for you :)

dist version

/*! bind v0.40 | (c) 2013, 2015 Remy Sharp | rem.mit-license.org */

dev version

/*!
 * bind.js v0.4.0
 * https://github.com/remy/bind/
 *
 * Copyright 2013, 2015 Remy Sharp
 * Released under the MIT license
 * http://rem.mit-license.org/
 */

Recommendation: Add index to array transform

I would recommend to add an index to the "transform" method of arrays like this:
Line 272:

let index = 0
    forEach(value, function (value) {
    html.push(transform(value, target, index++));
});

then you can use the index in your mapping to know where a user has clicked in a list.

transform: (value, bind, index) => {
     return '<li><label><input data-index="' + index + '" type="checkbox" ' + (value.done ? " checked" : "") + '/>' + value.text + '</label></li>'
}

Bind to attribute

I would like to know if its possible to bind between data and custom html attribute in way that attribute changes together with data?

Textarea

Is possible add textarea support?

Min file not updated for release

The bind.min.js file in the release for v1.1.1 and on master (and therefore in the jsbin examples) doesn't have the bug fix from v1.1.1; it's from the previous version.

<select> doesn't seem to work

In your example and in my test I can't get selects to update the bound object. The option gets set but changes to the chosen option aren't recognized. Tested with latest FF.

Pushing a new item in array doesn't 'watch' children property

If we have a structure like below and we push a new skills object, the orders property on the new inserted objected will not be AugmentedArray and so the watch on this array never happens:

Is this a bug, desired functionality, or am i missing something?
Thanks

var player = Bind({
  skills: [{
    name: 'skills0',
    orders: []
  }, {
    name: 'skills1',
    orders: [{
      orderName: 'order1',
      orderId: 1
    }, {
      orderName: 'order2',
      orderId: 2
    }, {
      orderName: 'order3',
      orderId: 3
    }]
  }]
}, {
  skills: {
    dom: '#skills',
    callback: function(value) {
      console.log(value);
    }
});

Data binding on `select` element

A few issues have been raised about select elements not working, and it's definitely right, I started to look at the issue and it raises some questions.

It sort of makes sense that if the bound object is an array, each element in the array would be an option element. However, the point of changing the value of a select is to set the value of the select not particularly change the values.

This kind of goes against how bind.js works, since you might think the closest comparison is the ul element, however, when the array is changed against the ul, the contents of the ul changes entirely.

I feel like the callback to the bound object in the case of a select, should give the new set value.

Issues around this are:

  1. What if the returned value is not in the option list of the select element?
  2. Can you initialise the select from the bound object? If so, I can't see how without a feature change.
  3. Anything else?

For reference, this is what a ul data binding looks like:

<ul id="ul"></ul>

<script src="../lib/bind.js"></script>
<script>
var data = new Bind({
  cats: ['one', 'two', 'three'],
}, {
  cats: {
    dom: '#ul',
    transform: c => `<li>${c}</li>`
  },
});
</script>

Selects don't work in v1.1.2

Hi, I'm sorry, but Selects don't work anymore in v1.1.2. In this Version, all Options are replaced by just the value of the select.

Support events fired at the root object

There's no mapping for the root object, so maybe this is a special key?

Something like:

var o = new Bind({
  cats: ['sam', 'nap', 'prince']
}, {
  ':root': callback
});

Akin to the [:root](http://www.w3.org/TR/css3-selectors/#root-pseudo) CSS selector.

Thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.