SkillAgentSearch skills...

JQuery.my

jQuery.my is a plugin that reactively binds form controls with js data structures.

Install / Use

/learn @ermouth/JQuery.my
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

jQuery.my

Below API description is not complete, see jquerymy.com for more detailed API, examples, and also for a list of all supported controls.

jquerymy is a plugin for complex reactive two-way data binding between DOM and state objects.

jquerymy recognizes standard HTML controls as well as composite controls rendered by jQuery UI widgets, Redactor, Ace, CodeMirror, Select2 and other plugins.

$.my provides comprehensive validation, conditional formatting and dependencies resolution. Apps can be nested – each $.my instance can be used as component of another $.my instance.

jquerymy also incorporates simple template engine and modal dialog mechanics.

See cloudwall.me as an example of web-app platform built on top of $.my. Another example of a large $.my app is Photon, an unofficial administrative panel for CouchDB.

Setup

jQuery.my requires jQuery 2.0+ and SugarJS 1.3.9–1.4.1.

<script src="/js/sugar.min.js"></script>
<script src="/js/jquery.min.js"></script>
<script src="/js/jquerymy.min.js"></script>

$.my can can be installed from npm – npm install jquerymy, same for bower.

Quick start

var person={};
var manifest = {
  'data': { name:'', metrics:{ age:'' }},
  'init': function ($node, formRuntimeObj) {
    $node.html(
      '<div><input id='name' type='text' /></div>' +
      '<div><input id='age' type='number' /></div>'
    );
  },
  'ui':{
    '#name': { bind: 'name' },
    '#age' : { bind: 'metrics.age' }
  }
};
// Init $.my
$('#form').my(manifest, person);

Now form inputs are filled with init values and any interaction with controls immediately mutates person object. Dot notation of deep-level bindings is just syntax sugar. It also can be used with arrays in style like someArray.1.

First param passed to $.my is denoted below as manifest.

Retrieving and updating data

To get form data just read value of the person variable or read $('#form').my('data'). Second way is good if $.my was initialized without any init value passed.

To put new data into already initialized instance of $.my call $('#form').my('data', {name: 'Mike'}). Note you can update data partially. Form is redrawn and revalidated after applying new data .

More complex data bind

The .bind property can be defined as a bi-directional function. It receives entire data object and new value as params. If null is passed function must only return value for DOM control, otherwise function must put value into data object and then return value for DOM.

So the bind function implements both getter and setter depending on value passed.

$('#form').my({
  ui:{
    '#name': 'name',
    '#age' : {
      bind: function (data, value, $control) {
        if (value != null) data.metrics.age = value; 
        return data.metrics.age = 
          (data.metrics.age + '').replace(/\D/g,'');
      }
    }
  }
}, person);

Note bind function in example does not allow to put anything than number. Pressing non-num key will does nothing with input, non-num chars are stripped immediately.

Third param $control is jQuery reference to the control being processed, it can be useful for navigating over form. Calling $control.my('find', '#name') returns #name control for example.

Validation

There are several ways to validate data received from control. Validator can be a regexp or a function. Functions unlike regexps can return custom error messages depending on value being checked. Check is performed just before executing .bind.

If value is incorrect .my-error class is applied to the closest DOM container of the control, otherwise this style rule is removed.

If control is not interactive – we bind some data with <div> element for example – .my-error class is applied to the element itself, not container.

RegExp validation

$('#form').my({
  ui:{
    '#name': { 
      bind: 'name', 
      check:/^[a-z]{10}$/i,
      error:'10 latin chars' // Optional
    },
    '#age':  { bind: 'metrics.age' }
  }
});

If user puts something different than 10-letter combination into #name input, the class attribute of the parent <div> is set to .my-error.

Validating with function

Validator function receives same params as .bind but executed before bind. Validator must return error message string – or empty string if value is ok.

Unlike .bind validator is never called with value equal to null, it always receives real value.

$('#form').my({
  data:{/*...*/},
  init: function ($node){/*...*/},
  ui:{
    '#name': {       
      'bind': 'name', 
      'check': function (data, value, $control) {
        if (value.length > 20) return 'Too long name'; 
        if (!/^[a-z]+$/.test(value)) return 'Only letters allowed'; 
        return '';
      }      
    },
    '#age': 'age'
  }
});

Messages returned by validator are put into DOM element with class .my-error-tip, which must be located inside the control’s container. So to make messages visible you must explicitly add this element into html. If no such element found error message will be added as title attribute to the control itself. If the control has own title, its value is stashed until error corrected.

<div>
  <input id="name" type="text" />
  <span class="my-error-tip"></span>
</div>

Checking entire form has no errors

$('#form').my('errors') returns an object which keys are invalid fields, and values are error messages. If all fields are ok, {} is returned. If form has children forms, their errors are mapped to appropriate branch.

To spot whether entire data is valid call $('#form').my('valid'), which returns true is everything is ok.

Dependencies

Let it be a form that calculates product of two values. We need to recalculate product each time any of factors changes.

$('#form').my({
  data:{ num1:'10', num2:'1.5' },
  init: function ($node){/*...*/},
  ui:{
    '#factor1': 'num1', 
    '#factor2': 'num2',
    '#product': {
      bind: function (data) {
        return data.num1 * data.num2;
      },
      watch: '#factor1,#factor2' //shorthand for ['#factor1', '#factor2']
    }
  }
});

Product is not mapped to data – .bind function does not save anything. It only returns value to put in #product DOM element. Every time #factor1 or #factor2 receive input #product is recalculated.

There is another syntax to define dependencies.

$('#form').my({
  ui:{
    '#factor1': {
      bind: 'num1', 
      recalc: '#product'
    },
    '#factor2': 'num2',
    '#product': {
      bind: function (data) {return data.num1 * data.num2},
      watch: '#factor2'
    }
  }
});

It behaves the same way. Note that .recalc is processed prior to .watch. So if a field depends on some other fields via both .recalc and .watch attributes, recalcs go first.

Loop dependencies are resolved correctly.

Conditional formatting and disabling

$.my can apply different classes depending on data object state.

$('#form').my({
  ui:{
    '#name': {       
      bind: 'name', 
      recalc: '#age',
      css: {
        'orange':/^.{10}$/
      }  
    },
    '#age': {
      bind: 'age',
      css:{
        ':disabled': function (data, value) {
          return data.name.length == 0;
        }
      }
    }
  }
});

Here if #name is exactly 10 chars, its container will receive class orange. If value doesn't match regexp then class orange is removed.

Input #age depends on value of #name field and is disabled if data.name is empty.

Conditional formatting over appropriate field is applied after .check and .bind.

Init functions

Preparing form during initialization

$('#form').my({
  data: { range: [30, 70] },
  init: function ($node) {
    $node.html('<input id="range" />')
  },
  ui:{
    '#range': {   
      init: function ($control) {
        $control.slider(range: true, min: 0, max: 100);
      },  
      bind: 'range'
    }
  }
});

Here we apply jQuery.UI Slider plugin over #range control. Data attribute range will receive array of two values – slider start and stop. On start control will be set to 30–70 range.

Certainly HTML carcass itself can be generated using init function, placed as child of manifest's root – as in above example.

Async init

To become async .init function must return promise of any sort (so-called ‘then-able’). Initialization sequence continues when the promise is resolved. If app promise is rejected, entire sequence also fails.

$('#form')
.my({
  data: { name:'' },
  init: function ($node, runtime) {
    var promise = $.ajax({
      url:'http://some.url'
    }).then(function (res) {
      // We received response, gen form HTML
      $node.html('<input id="name" type="text"/>')
      // Assume res is string, mount default data
      runtime.data.name = res;
    });
    
    return promise;
  },
  ui:{'#name': 'name'}
})
.then(function (data){
  // Do something when form init finished 
})
.fail(function(errMessage) {
  // Do something if init failed
});

jQuery AJAX implementation returns promise, so we may return $.ajax result directly. When data is received promise is resolved and initialization continues. When it is finished, promise returned by $.my is resolved with form’s .data.

Nested and repeated forms

Each DOM node which was instantiated with $.my can act as a single control for some parent $.my form. DOM node #child is instantiated with own manifest in example. ``

View on GitHub
GitHub Stars1.5k
CategoryDevelopment
Updated1d ago
Forks113

Languages

JavaScript

Security Score

80/100

Audited on Mar 30, 2026

No findings