Fork me on GitHub
Angularjs web应用

构建兼容浏览器的Angularjs web应用

背景

  随着mvvm逐渐成熟,现在使用jQuery构建web应用已经显得过时了,而且使用jQuery需要编写更多的代码去控制dom的取值、赋值、绑定事件等,而mvv从底层实现了对以上操作的支持,让程序员可以从原始的复杂、重复的编码中解放出来,让程序员可以将更多的重心放在业务的实现、数据的交互上去,而且大大减少了程序员需要编写的代码量。

  多年来的web经验告诉我们,编写web应用最难的地方主要在于浏览器的兼容问题,而且更多的兼容问题主要体现在ie上,因此只要能解决ie上的问题,那么这个web应用的其他问题都不再是问题了。

  今天,我将使用angualrjs来构建兼容浏览器的web应用,其实主要是解决ie下的一些问题,大致如下:

  • ie下构建app
  • ie下a标签unsafe问题
  • ie下ngSrc无法正确绑定问题
  • ie下ngMouseleave的问题
  • ie下创建service的一些问题
  • ie下使用swfupload的一些问题

  这里将使用angular的1.0.7版本,这是由于该版本对于ie低版本的支持较好,且存在的BUG较少,而高版本在ngShow、ngHide、ngBindHtml等标签上都会出现严重的问题。

ie下构建app

  在chrome、ff等浏览器下,我们可以直接在html或者body标签上直接绑定ngApp来绑定angularjs的应用,但是在ie下是无法实现的,这是由于低版本的ie不支持html5的一些特性以及JSON对象,因此我们需要引入2个js来解决这个问题:

  有了以上2个js文件后,ie下绑定ngApp的方式也略微有点区别,需要有class、ngApp、id的支持,代码如下:

<body class="ng-app:myApp" id="ng-app" ng-app="myApp">

ie下a标签unsafe问题

  当a标签的href绑定有效的url时,无法触发绑定其他事件,如:click,但是如果没有href或者href为空,在ie下则会出现unsafe的问题,因此需要给href绑定javascript:void(0),这样既能解决unsafe的问题又能解决无法触发事件的BUG。

ie下ngSrc无法正确绑定问题

  在ie下,不管是使用ng-src或者ng-src="{{ xx }}"都无法让img显示图片,而且在低版本ie下使用属性="{{xx}}"都是无法实现你想要的功能的,因此只能使用自定义的指令去实现某些不支持的标签。

  这里我自己实现了一个customSrc的指令去解决ie下ngSrc的问题,大致代码如下:

myApp.directive('customSrc', function () {
        return {
                scope: {
                        data: '='
                },
                link: function (scope, element, attrs) {
                        scope.$watch('data', function (v) {
                                if (!v)
                                        return;

                                element.attr('src', v);
                        });
                }
        };
});

ie下ngMouseleave的问题

  其实这不算是ng-mouseleave的bug,而是ie下,对于mouseleave和mouseover的表现不同而已,首先我们来看一段html代码,如:

<div ng-click="display=!display">下拉单</div>
<div ng-show="display" ng-mouseleave="display=false">
        <div>标题</div>
        <ul>
                <li>item1</li>
                <li>item2</li>
                <li>item3</li>
        </ul>
</div>

  在chrome、ff等浏览器下,这个脚本运行的还是很顺利的,但是在ie下会出现一个问题,就是当鼠标移入ul的时候,下拉单的列表层会消失,但是在ul内加上ng-mouseover="display=true"以后,这个BUG就可以解决了

ie下创建service的一些问题

  当我在创建模块的时候,如果模块只有一个方法时,我一般会直接返回这个函数,示例代码为:

//seajs
module.exports = function (){
        //coding
};

//angular
myApp.service('$fn', function (){
        return function (){
                //coding
        };
});

  在ie下,$fn()是无法正确调用的,会出现异常,因此需要做些许调整,代码如:

//angular
myApp.service('$fn', function (){
        return {
                exec: function (){
                        //coding
                }
        };
});

  在ie下使用$fn.exec()就不会出现问题了。

ie下使用swfupload的一些问题

  使用swfupload在低版本的ie下,最常出现的问题是flash的版本问题,因此首先得保证ie下flash的版本。

  偶尔会遇到"例外被抛出且未被接住",出现该异常的时候,主要注意2个问题:

  1、后台返回数据的时候,需要设置响应流的Head的ContentType为text/html; charset=UTF-8

  2、ie下绑定swfupload的button_placeholder的元素不能包含在ng-repeat内,如:

//html
<li ng-repeat="m in imgs">
        <a href="javascript:void(0)">
                <img data="m" width="178" height="178" custom-src />
        </a>
</li>

  当我们要将最后一个li的元素绑定到swfupload的button_placeholder上时,低版本ie下就会出现以上的错误,因此在使用当中要注意。

结尾

  以上是我在开发兼容浏览器angular应用遇到的一些问题以及解决方案,如果你有更好的解决方案,请告诉我。

  那么今天的文章就到这里了,如果文章中有什么问题请告诉我,谢谢!

 

背景

  当我们开发一个Web项目的时候,为了将图片管理与web服务分离开,通常都会搭建一个图片服务器。

  之所以选择nodejs是因为使用nodejs来搭建web项目相当简单而且快速,虽然这个图片服务器很简单,也有很多人会认为使用nodejs来当图片服务器不合适,但是当我们的应用没有达到非常大的程度的情况下,其实nodejs还是够用的。

  会使用到的工具如下:

  1. nodejs
  2. express(nodejs mvc框架)
  3. body-parser(express middleware)
  4. gm(nodejs中用来处理图片的)
  5. uuid(nodejs中用于生成uuid)
  6. underscore(nodejs数据处理库)
  7. ImageMagick(gm会调用该程序处理图片)

  那么接下来就来尝试实现这个简易的图片服务器吧, ^_^

搭建项目

  首先要使用express来搭建项目,由于图片服务器的功能相对简单,只有2个功能:1、获取图片资源 2、上传图片,因此对应express只需要使用到bodyParser这样的组件,代码大致如下:

//app.js
var app = require('express')();
process.app = app;//方便在其他地方使用app获取配置

require('./config')(__dirname, app);//所有配置
var mode = app.get('mode');
require('./controller/' + mode + 'Controller.js');

var config = app.get(mode);
require('http').createServer(app).listen(config.port, function () {
    console.log('%s已经启动,正监听:%s', config.name, config.port);
});

//config.js
var path = require('path');

var OPTIONS = {
        targetDir: function (app) {
                return path.join(app.get('rootDir'), 'img');
        },
        read: {
                name: '<<图片服务器(读取)>>',
                port: 80,
                'default': 'default.jpg',
                sizeReg: /(\w+)-(\d+)-(\d+)\.(\w+)$/
        },
        save: {
                name: '<<图片服务器(保存)>>',
                port: 9990
        },
        mode: 'read',
        contentType: {
                'jpg': 'image/jpeg',
                'jpeg': 'image/jpeg',
                'gif': 'image/gif',
                'png': 'image/png'
        }
};

module.exports = function (rootDir, app) {
    app.set('rootDir', rootDir);

    var $ = require('underscore');
    $.each(OPTIONS, function (v, k) {
        app.set(k, typeof v === 'function' ? v(app) : v);
    });

    app.use(require('body-parser')())
};

  上面独立出了config.js是为了将所有的配置放在一起方便维护,其次配置中的mode是为了区分当前是一个读取还是存储服务器。

获取图片

  这是图片服务器的其中一个功能,用户根据url获取图片服务器内存储的图片,当后台接收这个请求后,首先判断图片上会否存在,如果存在则返回对应的图片否则返回默认的图片,大致代码如下:

var path = require('path');
var fs = require('fs');
var gm = require('gm').subClass({ imageMagick: true });//默认的情况下 gm使用的是另外一个图片处理程序

var app = process.app;
var config = app.get('read');
var targetDir = app.get('targetDir');

app.get('/:filename', function (req, res) {
        var filePath = path.join(targetDir, req.params.filename);
        fs.exists(filePath, function (exists) {
                res.sendfile(exists ? filePath : path.join(targetDir, config.default));
        });
});

  使用以上的代码便可以对图片进行读取了,但是只能对targetDir目录下的文件进行读取且没有对文件类型进行判断,对文件类型的判断比较简单只要在方法执行之前对文件扩展名进行判断即可,至于增加了文件夹结构的话,跟直接从targetDir目录下读取是差不多的流程,稍微调整一下代码:

var contentTypes = app.get('contentType');

app.get('/:filename', function (req, res) {
        sendFile([], req.params.filename, res);
});

app.get('/:folder/:filename', function (req, res) {
        sendFile([req.params.folder], req.params.filename, res);
});

app.get('/:folder1/:folder2/:name', function (req, res) {
        sendFile([req.params.folder1, req.params.folder2], req.params.filename, res);
});

function sendFile(folders, filename, res) {
        var ext = path.extname(filename).substr(1);
        if (!contentTypes[ext])
                return res.sendfile(getFilePath());

        folders.push(filename);
        var filePath = getFilePath(path.join.apply(path, folders));
        fs.exists(filePath, function (exists) {
                res.sendfile(exists ? filePath : getFilePath());
        });
}

function getFilePath(filename) {
        return path.join(app.get('targetDir'), filename || config.default);
}

  接下来假设一个场景,如果上传了一张800*600的图片,但是在页面上显示的时候,也许我只想显示一张200*150的缩略图,这时候gm就该登场了,我们可以使用gm来为800*600的图片临时生成一张200*150的图片,并以buffer的形式回传给客户端,大致代码如下:

//判断请求图片是否存在
if (exists)                                                    
        return res.sendfile(filePath);    
                         
var groups = filename.match(config.sizeReg);                    
if (!groups)                                                    
        return res.sendfile(getFilePath());                        
                                                               
folders[len] = groups[1] + "." + groups[4];              
filePath = getFilePath(path.join.apply(path, folders));        
var width = parseInt(groups[2]);                                
var height = parseInt(groups[3]);  
//判断原始图是否存在                            
fs.exists(filePath, function (exists) {                        
        if (!exists)                                                
                filePath = getFilePath();                              
                                                               
        var gm = gm(filePath);                                      
        if (width > 0 && height > 0)                                
                gm.resize(width, height);                              
        gm.toBuffer(function (err, buffer) {                        
                if (err)                                                
                        return res.sendfile(getFilePath());                
                                                               
                res.set('Content-Type', contentTypes[ext]);            
                res.send(buffer);                                      
        });                                                        
});                                                            

上传图片

  由于图片服务器只提供最简单的功能,支持文件上传,但是并不会对上传的文件制作水印以及其他的处理,也不会将此功能开放到外网,因此图片上传服务器是在内网的,只能从web服务器那边处理图片以后上传到图片服务器,图片服务器对其进行存储并返回存储后的图片路径,大致代码如下:

var buf = require('buffer');
var fs = require('fs');
var path = require('path');
var util = require('util');
var gm = require('gm').subClass({ imageMagick: true });
var uuid = require('uuid');

var app = process.app;
var contentTypes = app.get('contentType');

/*
        请求包含如下参数:
        @ext    图片扩展名
        @buffer 图片buffer数据
        @folder 文件夹,格式:/aa/bb
*/
app.post('/', function (req, res) {
        var ext = req.body.ext;
        var buffer = req.body.buffer;
        if (!(ext && buffer && contentTypes[ext]))
                return res.json({ success: false });

        var pathArgs = req.body.folder.replace(/\n/g, '');
        if (pathArgs)
                pathArgs = pathArgs.substr(1).split('/');
        else
                pathArgs = [''];

        createDir(pathArgs);

        pathArgs.push('');
        var filePath = createPath(pathArgs, ext);
        gm(new buf.Buffer(JSON.parse(buffer))).write(filePath, function (err) {
                if (err)
                        return res.json({ success: false });

                res.json({ success: true, data: util.format('\\%s.%s', pathArgs.join('\\'), ext) });
        });
});

function createDir(pathArgs) {
        if (pathArgs[0]) {
                var dir = path.join(app.get('targetDir'), path.join.apply(path, pathArgs));
                var exists = fs.existsSync(dir);
                if (!exists)
                        fs.mkdirSync(dir);
        }
}

function createPath(pathArgs, ext) {
        var args = [app.get('targetDir')];
        pathArgs[pathArgs.length - 1] = uuid.v1().replace(/-/g, '');
        args.push.apply(args, pathArgs);
        var filePath = path.join.apply(path, args) + '.' + ext;
        return fs.existsSync(filePath) ? createPath(pathArgs, ext) : filePath;
}

结尾

  到这里我们就完成了一个nodejs版本的图片服务器了,因为尽可能将不需要的功能剥离到了其他的项目中去,因此图片服务器的功能就变得很简单、单一了。

  对于图片获取服务器生成临时大小的文件,可以先缓存起来,并结合leasing模式进行管理,频繁使用的情况下可以翻倍增加它的租约,到期则直接释放。

  而图片存储服务器,如果对于某些应用上传的图片都会生成固定系列大小的情况下, 可以使用gm对原始图进行二次处理,根据规则批量生成各种大小的图,至于图片的名字可以采用一些规则,这样返回的话,依然是存储后的原始图,而且格式则可以通过获取服务器获取,无需再进行缓存了。

  由于大部分的代码已经提供了,请不要再跟我要什么源码之类的哦,大家可以自己尝试搭建一下,今天就到这里了,如果有什么问题请告知我,谢谢各位。

 
 
分类: Javascriptmvcnodejs
 
分类: Javascript
posted on 2014-07-28 20:51  HackerVirus  阅读(334)  评论(0编辑  收藏  举报