如何组织Javascript UI?

时间:2009-10-16 09:51:49

标签: javascript

我需要有关程序架构的好例子和最佳实践。

我正在尝试为适用于Google.Maps的应用构建一个JS用户界面。在第一稿中,用户应该能够以类似于G.M的方式在地图上绘制几何形状。然后通过AJAX发送形状并显示响应。

问题是代码变得复杂只是用多边形编辑。

受Joel的“Duct-tape Programmer”的启发,我尝试绘制一个简单的代码,用于操作和切换事件处理程序,以避免大的if-else树。 “new poly”按钮为map.onclick创建一个观察者,更改其他按钮的事件处理程序或隐藏它们,并隐藏它们等等。

这种方法的缺点是数据处理代码与接口混合在一起。创建div容器以在新多边形上显示数据的代码位于处理w / G.M或w /形状数据的代码旁边。如果我想修改UI,我需要处理整个应用程序。

我稍后可以查看它并将这个生成UI的代码移到别处,但后来成为了我的首席程序员。相反,他坚持使用“消息传递”方法:一个简单的事件系统,其中对象订阅事件并触发它们。接口代码可以与数据处理和与G.M交谈完全隔离,但现在每个监听器都必须仔细检查此消息是否适用于它。

例如,单击地图上的多边形必须突出显示它并开始编辑。但是如果正在绘制另一个多边形而不是。那么,有些人是你在跟我说话吗? - 代码随处可见。

我会很感激UI架构方法的好例子。

5 个答案:

答案 0 :(得分:5)

向您建议的事件处理理念是一种很好的方法。

以下是一些更多的想法:

  • 使形状绘图事物成为一个组件
  • 形状绘图组件将事件发送到其他代码,以对“编辑”或“点击”等内容做出反应
  • 此组件还可以处理编辑部分 - 它将“clicked”事件发送到控制器,控制器告诉组件进入编辑模式
  • 在编辑模式下,组件在编辑关闭之前不会发送正常的“点击”事件,从而避免了您必须检查的问题

一般来说,最好有一个“视图”层,它只处理显示数据并将有关该数据的用户操作的事件(即点击等)发送到“控制器”层,然后决定做什么 - 例如,它可以决定将视图更改为编辑模式。

答案 1 :(得分:5)

我不知道这是否与此无关。但我把它作为我所有javascript项目的一个神殿。

(function () {
var window = this,
    $ = jQuery,
    controller,
    view,
    model;

controller = {
    addEventForMenu : function () {
        // Add event function for menu
    }
};

view = {
    content : {
        doStuff : function () {

        }
    },

    menu : {
        openMenuItem : function () {

        }
    }
};

model = {
    data :  {
        makeJson : function () {
            // make json of string
        },

        doAjax : function () {

        },

        handleResponse : function () {

        }
    }
}

$.extend(true, $.view, view);
})();

这里的好处是它只是将视图对象扩展到DOM,其余部分保存在匿名范围内。

同样在bug项目中,我为每个部分创建了这些文件,即map.js,content.js,editor.js

如果您只关心视图对象中方法的命名,那么在开发过程中您可以拥有任意数量的文件。当项目设置到生产环境时,我只需将其设为一个文件并缩小它。

..弗雷德里克

答案 2 :(得分:1)

简而言之,发布商 - 订阅者范例可以很好地制作几何形状。首先制作基本多边形发布者发布的原语命令行。 Canvas对象在这里看起来很明显,通常的方法repaint()用于更新客户端视图(通常在C中进行事件驱动编程,你可以查看例如opengl或glut eventdriven图形),结合我也使用的一般gmap API,发布者 - 订阅者无论图形实现,模式或工厂都是很好的设计模式。棘手的gmaps特定的东西是json和持久层之间的数组中的纬度和经度切换位置,还没有服务器端反向地理编码,命名有点不一致,并且对于多语言应用程序名称都改变相对用户的人类语言并且加倍(巴黎文本,巴黎在法国...),.看看您是否喜欢我的实施,使用geoip worldwide

注册地理名称和空间协调相对用户
function wAdd(response){
map.clearOverlays();
if(!response||response.Status.code!=200){

}
else{
    try{
        place=response.Placemark[0];
        point=new GLatLng(place.Point.coordinates[1],place.Point.coordinates[0]);
        marker=new GMarker(point);
        map.addOverlay(marker);
        marker.openInfoWindowHtml('<a href="/li?lat='+place.Point.coordinates[1]+'&lon='+place.Point.coordinates[0]+'&cc='+place.AddressDetails.Country.CountryNameCode+'">'+place.AddressDetails.Country.AdministrativeArea.Locality.LocalityName+'<span id="wr2"></span> '+ nads( place.Point.coordinates[1],place.Point.coordinates[0] )+' ' +'<img src="http://geoip.wtanaka.com/flag/'+place.AddressDetails.Country.CountryNameCode.toLowerCase()+'.gif">');
    }
    catch(e){
        try{
            place=response.Placemark[0];
            point=new GLatLng(place.Point.coordinates[1],place.Point.coordinates[0]);
            marker=new GMarker(point);
            map.addOverlay(marker);
            marker.openInfoWindowHtml('<a href="/li?lat='+place.Point.coordinates[1]+'&lon='+place.Point.coordinates[0]+'&cc='+place.AddressDetails.Country.CountryNameCode+'">'+place.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName+'<span id="wr2"></span> '+ nads( place.Point.coordinates[1],place.Point.coordinates[0] )+' ' +'<img src="http://geoip.wtanaka.com/flag/'+place.AddressDetails.Country.CountryNameCode.toLowerCase()+'.gif">');
        } 
        catch(e){
try {
        place=response.Placemark[0];
                point=new GLatLng(place.Point.coordinates[1],place.Point.coordinates[0]);
                marker=new GMarker(point);
                map.addOverlay(marker); 
                marker.openInfoWindowHtml('<a href="/li?lat='+place.Point.coordinates[1]+'&lon='+place.Point.coordinates[0]+'&cc='+place.AddressDetails.Country.CountryNameCode+'">'+place.AddressDetails.Country.CountryName+'<span id="wr2"></span> '+ nads( place.Point.coordinates[1],place.Point.coordinates[0] )+' ' +'<img src="http://geoip.wtanaka.com/flag/'+place.AddressDetails.Country.CountryNameCode.toLowerCase()+'.gif">');
            }
   catch(e){ 
place=response.Placemark[0];
      marker=new GMarker(point);
                map.addOverlay(marker);
marker.openInfoWindowHtml('<a href="/li">'+place.address+'</a>');
}

        } }
    }map.addOverlay(geoXml);
}

答案 3 :(得分:0)

我建议有几个包含状态的对象变量(0,绘图,编辑,......任何其他所需的) - 这将有助于您决定是否允许事件处理或只是掩埋它,例如绘图完成并单击在可编辑的多边形上发生。

关于用户界面 - 我不确定你的问题是针对你的 - 开发脚本还是用户,因为你在这里混合两件事。

请记住,对于用户来说,一切都应尽可能简单:如果他正在编辑,请不要让他画画。如果他正在画画,不要让他编辑(可能会出现多边形重叠)。但是 - 允许用户快速从编辑(例如右键单击?)切换到绘图 - 或者换句话说取消当前状态。

答案 4 :(得分:0)

我要做的第一件事是创建一个包装谷歌API的服务。如果您需要更改地图服务(Windows地图或雅虎地图),以后就可以了。然后你可以在google service上放置一个立面。然后,您可能希望在服务上放置一些包装器并将其拆分为视图(输出)和模型(输入),并使用控制器/演示者进行管理。在维基百科上查看模型视图控制器/模型视图Presenter / Presenter First / Humble Dialog。它应该讨论你寻找的分离。 Martin Fowler网页也进入了演示模式。你应该查看我的旧博客ugly-lisp-code。我引用了事件驱动/事件消息。

如果你有一对一的pub / sub只是在要触发事件的对象中存储一个事件处理程序(closure / lambda / first-order-function)。

如果你有一对多的pub / sub,那么你需要一个更复杂的对象来存储你的闭包。

LOL!现在我一直在看同样的问题。我将写一篇关于在JavaScript on my blog中使用presenter-first的文章。从presentermodel开始,只剩下一块骨头。

[edit]您可能想查看此stackoverflow question。其中一个答案是将关注点分离为MVC的链接。该链接位于A List Apart。

相关问题