阿贾克斯数据的双向数据绑定策略?数据、绑定、双向、策略

2023-09-10 16:09:45 作者:听风挽月.

我想 1)绘制创建表单域,并使用从JavaScript对象数据填充 2)更新这些支持对象时的表单字段的值更改

1号很容易。我有我已经使用了一些JS模板系统工作得相当好。

2号可能需要一点心思。在AJAX数据绑定一个快速谷歌搜索变成了几个系统,这似乎基本上是单向的。它们被设计用来更新基于备份的JS对象的用户界面,但似乎并没有解决如何更改时到UI更新这些支持对象的问题。任何人都可以推荐这将做到这一点对我来说任何库?这件事情我可以写我自己没有太多的麻烦,但是,如果这个问题已经经过深思熟虑,我宁可不重复的工作。

//////////////////////编辑/////////////////

我已经创建了自己的jQuery插件来实现这一点。这里是。请让我知道,如果它是有用的,如果你认为它可能是值得使它更加正式。也让我知道,如果你有问题或问题。

  / *

    获取一个jQuery对象,并结合其表单元素与后备JavaScript对象。有两个参数:对象
    要绑定到,和一个可选的的ChangeListener,它必须实施changeHappened的方法。

    例:

    // ============================
    // =支持对象=
    // ============================

    DemoBean创建= {
        为prop1:VAL
        prop2:
            {nestedObjProp:VAL},
            {nestedObjProp:VAL}
        ]
        prop3:
            stringVal1
            stringVal12
        ]
    }

    // ===========================
    // =表单字段=
    // ===========================

    <输入类=绑定NAME =prop2 [1] .nestedObjProp>


    // ===========================
    // =请求=
    // ===========================

    $ JQ(绑定)。bindData(
        DemoBean创建,
        {changeHappened:函数(){执行console.log(变)}}
    )


* /


(函数($){


    //返回属性在给定的路径找到的值
    //加一个功能,你可以用它来设置该属性
    VAR navigateObject =功能(parentObj,pathArg){
        VAR immediateParent = parentObj;
        VAR路径= pathArg
            。更换([, 。)
            。更换(], )
            。更换(]。, 。)
            。分裂(。);
        为(变种I = 0; I&≤(path.length-1);我++){
            VAR currentPathKey =路径[I]
            immediateParent = immediateParent [currentPathKey]
            如果(immediateParent === NULL){
                抛出新的错误(+通路的bindData插件遇到一个空值[我] +路径+路径);
            }
        }

        返回 {
            值:immediateParent [路径[path.length  -  1],
            设置:功能(VAL){
                immediateParent [路径[path.length  -  1] = VAL
            },
            deleteObj:函数(){
                如果($。IsArray的(immediateParent)){
                    immediateParent.splice(路径[path.length  -  1],1);
                }其他{
                    删除immediateParent [路径[path.length  -  1];
                }
            }
        }

    }

    VAR的isEmpty =功能(STR){
        返回STR == NULL ||海峡==;
    }

    VAR bindData =功能(parentObj,的ChangeListener){

        VAR parentObj,
            单选按钮= [];
        VAR的ChangeListener;
        VAR设置;
        VAR defaultSettings = {
            //如果这个标志是真实的,你可以把一个标签在一个领域,
            //喜欢<输入值=电话号码/>和值
            //将不会通过在parentObj一个空白值代替
            //另外,如果用户点击的字段,该字段将被清除。
            allowLabelsInfields:真
        };

        //允许两种形式:
        //功能(parentObj,的ChangeListener)
        //和功能(设置)。
        如果(的arguments.length == 2){
            parentObj =参数[0];
            的ChangeListener =参数[1]
            设置= defaultSettings;
        }其他{
            设置= $ jq.extend(defaultSettings,参数[0]);
            parentObj = settings.parentObj;
            的ChangeListener = settings.changeListener;
        }

        VAR changeHappened =功能(){};
        如果(typeof运算的ChangeListener!=未定义){
            如果(typeof运算changeListener.changeHappened ==功能){
                changeHappened = changeListener.changeHappened;
            }其他{
                抛出新的错误(一个ChangeListener必须'changeHappened一个调用的方法。);
            }
        };
        this.each(功能(键,VAL){
            VAR formElem = $(VAL);
            。VAR标签名= formElem.attr(变量名)与toLowerCase();
            VAR FIELDTYPE;
          如果(标签名==输入){
            FIELDTYPE = formElem.attr(类型)与toLowerCase()。
          }其他{
            FIELDTYPE =标签名;
          }


            //使用name属性,因为我们想绑定到财产的地址。
            //除非它是一个单选按钮,在这种情况下,用价值,因为名是该组的名称
            //这应该适用于任意深度嵌套数据。
            VAR navigationResult = navigateObject(parentObj,formElem.attr(FIELDTYPE ===单选,值:名字));

            //填充字段中的支持对象数据

            开关(FIELDTYPE){

        //它是一个单选按钮?如果是这样,检查或不基于
        // navigationResult.value的布尔值
        案广播:
          radioButtons.push(formElem);
          formElem.data(bindDataPlugin,{navigationResult:navigationResult});
          formElem.attr(选中,navigationResult.value);
          formElem.change(函数(){
            //单选按钮似乎只是_selected_何时更新,不
            //取消的时候。所以,如果一个人点击,更新绑定
            //为所有这些对象。我知道这是一个有点难看,
            //但它的工作原理。
            $ jq.each(单选按钮,功能(指数按钮){
              VAR对接= $ JQ(按钮);
              butt.data(bindDataPlugin)navigationResult.set(butt.attr(选中))。
            });
            navigationResult.set(formElem.attr(选中));
            changeHappened();
          });
          打破;

        案文:
          //如果useFieldLabel为真,则表示该场是
          //自带标签。例如,只有一封电子邮件领域
          //默认值是输入电子邮件。
          VAR useFieldLabel =的isEmpty(navigationResult.value)
                   &功放;&安培; !的isEmpty(formElem.val())
                   &功放;&安培; settings.allowLabelsInfields;
          如果(useFieldLabel){
           变种labelText = formElem.val();
           formElem.click(函数(){
             如果(formElem.val()=== labelText){
               formElem.val();
             }
           })
          }其他{
           formElem.attr(价值,navigationResult.value);
          }
          formElem.keyup(函数(){
           navigationResult.set(formElem.attr(价值));
           changeHappened();
          });

          打破;

        案选择:
          变种domElem = formElem.get(0);
                $ jq.each(domElem.options,功能(索引选项){
                    如果(option.value === navigationResult.value){
                        domElem.selectedIndex =指数;
                    }
                });
                formElem.change(函数(){
                    navigationResult.set(formElem.val());
                })
          打破;

        案文本区域:
          formElem.text(navigationResult.value);
          formElem.keyup(函数(){
           changeHappened();
           navigationResult.set(formElem.val());
          });
          打破;
      }


        });
        回到这一点;
    };

    bindData.navigateObject = navigateObject;

    $ .fn.bindData = bindData;

})(jQuery的);
 

解决方案

有万吨那里库来实现你想要的。

对于初学者来说,可以使用 DWR ,以获得Ajax功能。这被触发表单字段的的onChange 事件的方法应该进行DWR调用相应的支持对象

希望这有助于!

10分钟读懂数据vue响应式和双向绑定原理

I'd like to 1) Draw create form fields and populate them with data from javascript objects 2) Update those backing objects whenever the value of the form field changes

Number 1 is easy. I have a few js template systems I've been using that work quite nicely.

Number 2 may require a bit of thought. A quick google search on "ajax data binding" turned up a few systems which seem basically one-way. They're designed to update a UI based on backing js objects, but don't seem to address the question of how to update those backing objects when changes are made to the UI. Can anyone recommend any libraries which will do this for me? It's something I can write myself without too much trouble, but if this question has already been thought through, I'd rather not duplicate the work.

////////////////////// edit /////////////////

I've created my own jquery plugin to accomplish this. Here it is. Please let me know if it's useful and if you think it might be worth making it more "official". Also let me know if you have problems or questions.

/*

    Takes a jquery object and binds its form elements with a backing javascript object. Takes two arguments: the object 
    to be bound to, and an optional "changeListener", which must implement a "changeHappened" method.

    Example: 

    // ============================
    // =     backing object       =
    // ============================

    demoBean = {
        prop1 : "val",
        prop2 : [
            {nestedObjProp:"val"},
            {nestedObjProp:"val"}
        ],
        prop3 : [
            "stringVal1",
            "stringVal12"
        ]
    }

    // ===========================
    // =        FORM FIELD       =
    // ===========================

    <input class="bindable" name="prop2[1].nestedObjProp">


    // ===========================
    // =       INVOCATION        =
    // ===========================

    $jq(".bindable").bindData( 
        demoBean, 
        {changeHappened: function(){console.log("change")}}
    )


*/


(function($){


    // returns the value of the property found at the given path
    // plus a function you can use to set that property
    var navigateObject = function(parentObj, pathArg){
        var immediateParent = parentObj;
        var path = pathArg
            .replace("[", ".")
            .replace("]", "")
            .replace("].", ".")
            .split(".");
        for(var i=0; i< (path.length-1); i++){
            var currentPathKey = path[i];
            immediateParent = immediateParent[currentPathKey];
            if(immediateParent === null){
                throw new Error("bindData plugin encountered a null value at  " + path[i] + " in path" + path);
            }
        }

        return {
            value: immediateParent[path[path.length - 1]],
            set: function(val){
                immediateParent[path[path.length - 1]] = val
            },
            deleteObj: function(){
                if($.isArray(immediateParent)){
                    immediateParent.splice(path[path.length - 1], 1);
                }else{
                    delete  immediateParent[path[path.length - 1]];
                }
            } 
        }

    }

    var isEmpty = function(str){
        return str == null || str == "";
    }

    var bindData = function(parentObj, changeListener){

        var parentObj,
            radioButtons = [];
        var changeListener;
        var settings;
        var defaultSettings = {
            // if this flag is true, you can put a label in a field,
            // like <input value="Phone Number"/>, and the value
            // won't be replaced by a blank value in the parentObj
            // Additionally, if the user clicks on the field, the field will be cleared.
            allowLabelsInfields: true 
        };

        // allow two forms: 
        // function(parentObj, changeListener)
        // and function(settings). 
        if(arguments.length == 2){
            parentObj = arguments[0];
            changeListener = arguments[1]
            settings = defaultSettings;
        }else{  
            settings = $jq.extend(defaultSettings, arguments[0]);
            parentObj = settings.parentObj;
            changeListener = settings.changeListener;
        }

        var changeHappened = function(){};
        if(typeof changeListener != "undefined"){
            if(typeof changeListener.changeHappened == "function"){
                changeHappened = changeListener.changeHappened;
            }else{
                throw new Error("A changeListener must have a method called 'changeHappened'.");
            }
        };
        this.each(function(key,val){
            var formElem = $(val);
            var tagName = formElem.attr("tagName").toLowerCase();
            var fieldType;
          if(tagName == "input"){
            fieldType = formElem.attr("type").toLowerCase();
          }else{
            fieldType = tagName;
          }


            // Use the "name" attribute as the address of the property we want to bind to.
            // Except if it's a radio button, in which case, use the "value" because "name" is the name of the group
            // This should work for arbitrarily deeply nested data. 
            var navigationResult = navigateObject(parentObj, formElem.attr(fieldType === "radio"? "value" : "name"));

            // populate the field with the data in the backing object

            switch(fieldType){

        // is it a radio button? If so, check it or not based on the 
        // boolean value of navigationResult.value
        case "radio":
          radioButtons.push(formElem);
          formElem.data("bindDataPlugin", {navigationResult: navigationResult});
          formElem.attr("checked", navigationResult.value);
          formElem.change(function(){
            // Radio buttons only seem to update when _selected_, not 
            // when deselected. So if one is clicked, update the bound
            // object for all of them. I know it's a little ugly,
            // but it works.
            $jq.each(radioButtons, function(index, button){
              var butt = $jq(button);
              butt.data("bindDataPlugin").navigationResult.set(butt.attr("checked"));
            });
            navigationResult.set(formElem.attr("checked"));           
            changeHappened();
          });
          break;

        case "text":
          // if useFieldLabel is true, it means that the field is 
          // self-labeling. For example, an email field whose 
          // default value is "Enter Email".
          var useFieldLabel = isEmpty( navigationResult.value )
                   && !isEmpty( formElem.val() )  
                   && settings.allowLabelsInfields;
          if(useFieldLabel){
           var labelText = formElem.val();
           formElem.click(function(){
             if(formElem.val() === labelText){
               formElem.val("");
             }
           })
          }else{
           formElem.attr("value", navigationResult.value);
          }
          formElem.keyup(function(){
           navigationResult.set(formElem.attr("value"));
           changeHappened();
          });

          break;

        case "select":
          var domElem = formElem.get(0);
                $jq.each(domElem.options, function(index, option){
                    if(option.value === navigationResult.value){
                        domElem.selectedIndex = index;
                    }
                });
                formElem.change(function(){
                    navigationResult.set(formElem.val());
                })
          break;

        case "textarea":
          formElem.text(navigationResult.value);
          formElem.keyup(function(){
           changeHappened();
           navigationResult.set(formElem.val());
          });
          break;
      }


        });
        return this;
    };

    bindData.navigateObject = navigateObject;

    $.fn.bindData = bindData;

})(jQuery);

解决方案

There are tons of libraries out there to achieve what you want.

For starters, you can use DWR to get the Ajax functionality. The method that gets triggered for the form field's onChange event should make a DWR call to the corresponding backing object

Hope this helps!